mirror of
https://github.com/node-red/node-red-nodes.git
synced 2023-10-10 13:36:58 +02:00
Merge branch 'node-red:master' into master
This commit is contained in:
commit
2f73c59d7c
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-pi-sense-hat",
|
||||
"version" : "0.1.4",
|
||||
"version" : "0.1.5",
|
||||
"description" : "A Node-RED node to interact with a Raspberry Pi Sense HAT",
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
|
@ -66,7 +66,7 @@ hf_interval = 0.09 # Approx 10/s
|
||||
lf_interval = 1
|
||||
|
||||
hf_enabled = False
|
||||
lf_enabled = False
|
||||
lf_enabled = True
|
||||
|
||||
scroll = None
|
||||
|
||||
|
@ -1 +1,56 @@
|
||||
[{"id":"8b346a30.651a88","type":"rpi-sensehat out","z":"7756eff1.08d0a","name":"","x":530,"y":220,"wires":[]},{"id":"c2e7a15d.237bd","type":"function","z":"7756eff1.08d0a","name":"Simple graphical clock","func":"\n// array to hold \"random\" pixels\nvar ranNums = [];\n\n// create a non-overlapping array of random numbers 0-8\nfunction rerand() {\n var nums = [0,1,2,3,4,5,6,7,8];\n var i = nums.length;\n var j;\n ranNums = [];\n while (i--) {\n j = Math.floor(Math.random() * (i+1));\n ranNums.push(nums[j]);\n nums.splice(j,1);\n }\n}\n\n// Get the hours and minutes and split into tens and units\nvar d = new Date();\nvar s = d.getSeconds();\nvar su = s%4;\nif (su === 0) {\n var h = d.getHours();\n var m = d.getMinutes();\n var hu = h%10;\n h = parseInt(h/10);\n var mu = m%10;\n m = parseInt(m/10);\n \n // Do the tens of hours (red)\n rerand();\n node.send({payload:\"1-3,1-3,0,0,0\"});\n for (var i=0; i<h; i++) {\n node.send({payload:(1+ranNums[i]%3)+\",\"+(1+parseInt(ranNums[i]/3))+\",255,0,0\"});\n }\n \n // Do the units of hours (green)\n rerand();\n node.send({payload:\"4-6,1-3,0,0,0\"});\n for (var i=0; i<hu; i++) {\n node.send({payload:(4+ranNums[i]%3)+\",\"+(1+parseInt(ranNums[i]/3))+\",0,255,0\"});\n }\n \n // Do the tens of minutes (yellow)\n rerand();\n node.send({payload:\"1-3,4-6,0,0,0\"});\n for (var i=0; i<m; i++) {\n node.send({payload:(1+ranNums[i]%3)+\",\"+(4+parseInt(ranNums[i]/3))+\",255,255,0\"});\n }\n \n // Do the unit of minutes (blue)\n rerand();\n node.send({payload:\"4-6,4-6,0,0,0\"});\n for (var i=0; i<mu; i++) {\n node.send({payload:(4+ranNums[i]%3)+\",\"+(4+parseInt(ranNums[i]/3))+\",0,0,255\"});\n }\n \n}\nnode.send({payload:\"0,0,\"+(su===0?\"purple\":\"off\")+\",7,0,\"+(su===1?\"purple\":\"off\")+\",7,7,\"+(su===2?\"purple\":\"off\")+\",0,7,\"+(su===3?\"purple\":\"off\")})\n\n\n// nothing left to do\nreturn null;","outputs":1,"noerr":0,"x":320,"y":220,"wires":[["8b346a30.651a88"]]},{"id":"fc3808df.6cbcf8","type":"comment","z":"7756eff1.08d0a","name":"Simple graphical clock for Sense Hat","info":"Generates a graphical clock, showing hours and minutes. \n\nHours are shown by the red and green pixels, minutes\nare the yellow and blue pixels.\n\nUses the local time of the Pi - you may need to set your local\ntimezone using\n\n sudo dpkg-reconfigure tzdata\n","x":210,"y":180,"wires":[]},{"id":"f33ea1a7.27216","type":"inject","z":"7756eff1.08d0a","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":true,"x":120,"y":220,"wires":[["c2e7a15d.237bd"]]}]
|
||||
[
|
||||
{
|
||||
"id": "8b346a30.651a88",
|
||||
"type": "rpi-sensehatsim out",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "",
|
||||
"x": 530,
|
||||
"y": 220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "c2e7a15d.237bd",
|
||||
"type": "function",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "Simple graphical clock",
|
||||
"func": "\n// array to hold \"random\" pixels\nvar ranNums = [];\n\n// create a non-overlapping array of random numbers 0-8\nfunction rerand() {\n var nums = [0,1,2,3,4,5,6,7,8];\n var i = nums.length;\n var j;\n ranNums = [];\n while (i--) {\n j = Math.floor(Math.random() * (i+1));\n ranNums.push(nums[j]);\n nums.splice(j,1);\n }\n}\n\n// Get the hours and minutes and split into tens and units\nvar d = new Date();\nvar s = d.getSeconds();\nvar su = s%4;\nif (su === 0) {\n var h = d.getHours();\n var m = d.getMinutes();\n var hu = h%10;\n h = parseInt(h/10);\n var mu = m%10;\n m = parseInt(m/10);\n \n // Do the tens of hours (red)\n rerand();\n node.send({payload:\"1-3,1-3,0,0,0\"});\n for (var i=0; i<h; i++) {\n node.send({payload:(1+ranNums[i]%3)+\",\"+(1+parseInt(ranNums[i]/3))+\",255,0,0\"});\n }\n \n // Do the units of hours (green)\n rerand();\n node.send({payload:\"4-6,1-3,0,0,0\"});\n for (var i=0; i<hu; i++) {\n node.send({payload:(4+ranNums[i]%3)+\",\"+(1+parseInt(ranNums[i]/3))+\",0,255,0\"});\n }\n \n // Do the tens of minutes (yellow)\n rerand();\n node.send({payload:\"1-3,4-6,0,0,0\"});\n for (var i=0; i<m; i++) {\n node.send({payload:(1+ranNums[i]%3)+\",\"+(4+parseInt(ranNums[i]/3))+\",255,255,0\"});\n }\n \n // Do the unit of minutes (blue)\n rerand();\n node.send({payload:\"4-6,4-6,0,0,0\"});\n for (var i=0; i<mu; i++) {\n node.send({payload:(4+ranNums[i]%3)+\",\"+(4+parseInt(ranNums[i]/3))+\",0,0,255\"});\n }\n \n}\nnode.send({payload:\"0,0,\"+(su===0?\"purple\":\"off\")+\",7,0,\"+(su===1?\"purple\":\"off\")+\",7,7,\"+(su===2?\"purple\":\"off\")+\",0,7,\"+(su===3?\"purple\":\"off\")})\n\n\n// nothing left to do\nreturn null;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"x": 320,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"8b346a30.651a88"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fc3808df.6cbcf8",
|
||||
"type": "comment",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "Simple graphical clock for Sense Hat",
|
||||
"info": "Generates a graphical clock, showing hours and minutes. \n\nHours are shown by the red and green pixels, minutes\nare the yellow and blue pixels.\n\nUses the local time of the Pi - you may need to set your local\ntimezone using\n\n sudo dpkg-reconfigure tzdata\n",
|
||||
"x": 210,
|
||||
"y": 180,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "f33ea1a7.27216",
|
||||
"type": "inject",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "",
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"repeat": "1",
|
||||
"crontab": "",
|
||||
"once": true,
|
||||
"x": 120,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"c2e7a15d.237bd"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1 +1,53 @@
|
||||
[{"id":"104a54e7.6656cb","type":"rpi-sensehat out","z":"7756eff1.08d0a","name":"","x":470,"y":300,"wires":[]},{"id":"a3786fbb.b3ff9","type":"rpi-sensehat in","z":"7756eff1.08d0a","name":"","motion":true,"env":false,"stick":false,"x":120,"y":300,"wires":[["931798d.9640d68"]]},{"id":"931798d.9640d68","type":"function","z":"7756eff1.08d0a","name":"Compass","func":"// Based on the Compass example provided by\n// the Sense HAT python library\n// https://github.com/RPi-Distro/python-sense-hat/tree/master/examples\n\nvar led_loop = [4, 5, 6, 7, 15, 23, 31, 39, 47, 55, 63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3];\nvar led_degree_ratio = led_loop.length / 360.0;\nvar dir = (360 - msg.payload.compass)%360;\n\nfunction getXY(dir) {\n var led_index = Math.floor(led_degree_ratio * (dir%360));\n var offset = led_loop[led_index];\n return [offset % 8,Math.floor(offset / 8)];\n}\n\nvar previous = context.get('previous');\nmsg.payload = '';\n\nvar position = getXY(dir);\nif (!previous || position[0] != previous[0][0] || position[1] != previous[0][1]) {\n if (previous) {\n msg.payload = previous[0][0]+\",\"+previous[0][1]+\",off,\"+\n previous[1][0]+\",\"+previous[1][1]+\",off,\";\n } else {\n msg.payload = \"*,*,off,\";\n }\n previous = [\n position,\n getXY(dir+180)\n ];\n msg.payload += previous[0][0]+\",\"+previous[0][1]+\",red,\"+\n previous[1][0]+\",\"+previous[1][1]+\",white\"\n \n context.set('previous',previous);\n return msg;\n}\nreturn null;\n\n","outputs":1,"noerr":0,"x":280,"y":300,"wires":[["104a54e7.6656cb"]]},{"id":"434680a8.c2e54","type":"comment","z":"7756eff1.08d0a","name":"Simple compass for Sense Hat","info":"Generates a compass, showing north (red) and south (white).\n\nBased on the python example provided with the Sense Hat: https://github.com/RPi-Distro/python-sense-hat/tree/master/examples\n\nThe accuracy of the compass will depend on callibrating the Sense Hat\ninternal compass. Search the Sense Hat forums for a guide on how to\ndo this.\n","x":190,"y":260,"wires":[]}]
|
||||
[
|
||||
{
|
||||
"id": "104a54e7.6656cb",
|
||||
"type": "rpi-sensehatsim out",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "",
|
||||
"x": 470,
|
||||
"y": 300,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "a3786fbb.b3ff9",
|
||||
"type": "rpi-sensehatsim in",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "",
|
||||
"motion": true,
|
||||
"env": false,
|
||||
"stick": false,
|
||||
"x": 120,
|
||||
"y": 300,
|
||||
"wires": [
|
||||
[
|
||||
"931798d.9640d68"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "931798d.9640d68",
|
||||
"type": "function",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "Compass",
|
||||
"func": "// Based on the Compass example provided by\n// the Sense HAT python library\n// https://github.com/RPi-Distro/python-sense-hat/tree/master/examples\n\nvar led_loop = [4, 5, 6, 7, 15, 23, 31, 39, 47, 55, 63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3];\nvar led_degree_ratio = led_loop.length / 360.0;\nvar dir = (360 - msg.payload.compass)%360;\n\nfunction getXY(dir) {\n var led_index = Math.floor(led_degree_ratio * (dir%360));\n var offset = led_loop[led_index];\n return [offset % 8,Math.floor(offset / 8)];\n}\n\nvar previous = context.get('previous');\nmsg.payload = '';\n\nvar position = getXY(dir);\nif (!previous || position[0] != previous[0][0] || position[1] != previous[0][1]) {\n if (previous) {\n msg.payload = previous[0][0]+\",\"+previous[0][1]+\",off,\"+\n previous[1][0]+\",\"+previous[1][1]+\",off,\";\n } else {\n msg.payload = \"*,*,off,\";\n }\n previous = [\n position,\n getXY(dir+180)\n ];\n msg.payload += previous[0][0]+\",\"+previous[0][1]+\",red,\"+\n previous[1][0]+\",\"+previous[1][1]+\",white\"\n \n context.set('previous',previous);\n return msg;\n}\nreturn null;\n\n",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"x": 280,
|
||||
"y": 300,
|
||||
"wires": [
|
||||
[
|
||||
"104a54e7.6656cb"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "434680a8.c2e54",
|
||||
"type": "comment",
|
||||
"z": "7756eff1.08d0a",
|
||||
"name": "Simple compass for Sense Hat",
|
||||
"info": "Generates a compass, showing north (red) and south (white).\n\nBased on the python example provided with the Sense Hat: https://github.com/RPi-Distro/python-sense-hat/tree/master/examples\n\nThe accuracy of the compass will depend on callibrating the Sense Hat\ninternal compass. Search the Sense Hat forums for a guide on how to\ndo this.\n",
|
||||
"x": 190,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name" : "node-red-node-serialport",
|
||||
"version" : "1.0.3",
|
||||
"version" : "1.0.4",
|
||||
"description" : "Node-RED nodes to talk to serial ports",
|
||||
"dependencies" : {
|
||||
"serialport" : "^10.5.0"
|
||||
"serialport" : "^11.0.0"
|
||||
},
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
|
@ -8,6 +8,18 @@
|
||||
<label for="node-input-topic" style="width: 110px;"><i class="fa fa-envelope"></i> Destination</label>
|
||||
<input type="text" id="node-input-topic" placeholder="topic or queue">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-ack" style="width: 110px;"><i class="fa fa-check"></i> Message acknowledgment</label>
|
||||
<select type="text" id="node-input-ack" style="display: inline-block; width: 250px; vertical-align: top;">
|
||||
<option value="auto">Auto</option>
|
||||
<option value="client">Client</option>
|
||||
<option value="client-individual">Client individual (only v1.1)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
Enabling the ACK (acknowledgment) will set the <code>ack</code> header to <code>client</code> while subscribing to topics.
|
||||
This means the items on the broker queue will not be dequeue'd unless an ACK message is sent using the <code>ack</code> node.
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name" style="width: 110px;"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
@ -33,6 +45,7 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"stomp-server",required:true},
|
||||
ack: {value: "auto", required: true},
|
||||
topic: {value:"",required:true}
|
||||
},
|
||||
inputs:0,
|
||||
@ -148,8 +161,8 @@
|
||||
port: {value:61613,required:true,validate:RED.validators.number()},
|
||||
protocolversion: {value:"1.0",required:true},
|
||||
vhost: {},
|
||||
reconnectretries: {value:"0",required:true,validate:RED.validators.number()},
|
||||
reconnectdelay: {value:"0.5",required:true,validate:RED.validators.number()},
|
||||
reconnectretries: {value:0,required:true,validate:RED.validators.number()},
|
||||
reconnectdelay: {value:1,required:true,validate:RED.validators.number()},
|
||||
name: {}
|
||||
},
|
||||
credentials: {
|
||||
@ -161,3 +174,56 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="stomp ack">
|
||||
<div class="form-row">
|
||||
<label for="node-input-server" style="width: 110px;"><i class="fa fa-bookmark"></i> Server</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic" style="width: 110px;"><i class="fa fa-envelope"></i> Destination</label>
|
||||
<input type="text" id="node-input-topic" placeholder="topic or queue">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name" style="width: 110px;"><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="stomp ack">
|
||||
<p>
|
||||
ACK is used to acknowledge consumption of a message from a subscription using client acknowledgment. When a client has issued a SUBSCRIBE frame with the ack header set to client any messages received from that destination will not be considered to have been consumed (by the server) until the message has been acknowledged via an ACK.
|
||||
</p>
|
||||
<p>
|
||||
The node allows for following inputs:
|
||||
<ul>
|
||||
<li><code>msg.messageId</code>: The id of the message to acknowledge</li>
|
||||
<li><code>msg.transaction</code>: Optional transaction name</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
See the <a href="https://stomp.github.io/stomp-specification-1.0.html#frame-ACK" target="new">Stomp 1.0 spec</a> for more details.
|
||||
</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('stomp ack',{
|
||||
category: 'output',
|
||||
color:"#e8cfe8",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"stomp-server",required:true},
|
||||
topic: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "bridge.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||"stomp";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return (this.name)?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -2,94 +2,406 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var StompClient = require('stomp-client');
|
||||
var querystring = require('querystring');
|
||||
|
||||
// ----------------------------------------------
|
||||
// ------------------- State --------------------
|
||||
// ----------------------------------------------
|
||||
function updateStatus(node, allNodes) {
|
||||
let setStatus = setStatusDisconnected;
|
||||
|
||||
if (node.connecting) {
|
||||
setStatus = setStatusConnecting;
|
||||
} else if (node.connected) {
|
||||
setStatus = setStatusConnected;
|
||||
}
|
||||
|
||||
setStatus(node, allNodes);
|
||||
}
|
||||
|
||||
function setStatusDisconnected(node, allNodes) {
|
||||
if (allNodes) {
|
||||
for (let id in node.users) {
|
||||
if (hasProperty(node.users, id)) {
|
||||
node.users[id].status({ fill: "red", shape: "ring", text: "node-red:common.status.disconnected"});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.status({ fill: "red", shape: "ring", text: "node-red:common.status.disconnected" })
|
||||
}
|
||||
}
|
||||
|
||||
function setStatusConnecting(node, allNodes) {
|
||||
if(allNodes) {
|
||||
for (var id in node.users) {
|
||||
if (hasProperty(node.users, id)) {
|
||||
node.users[id].status({ fill: "yellow", shape: "ring", text: "node-red:common.status.connecting" });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.status({ fill: "yellow", shape: "ring", text: "node-red:common.status.connecting" });
|
||||
}
|
||||
}
|
||||
|
||||
function setStatusConnected(node, allNodes) {
|
||||
if(allNodes) {
|
||||
for (var id in node.users) {
|
||||
if (hasProperty(node.users, id)) {
|
||||
node.users[id].status({ fill: "green", shape: "dot", text: "node-red:common.status.connected" });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.status({ fill: "green", shape: "dot", text: "node-red:common.status.connected" });
|
||||
}
|
||||
}
|
||||
|
||||
function setStatusError(node, allNodes) {
|
||||
if(allNodes) {
|
||||
for (var id in node.users) {
|
||||
if (hasProperty(node.users, id)) {
|
||||
node.users[id].status({ fill: "red", shape: "dot", text: "error" });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.status({ fill: "red", shape: "dot", text: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
// ------------------- Nodes --------------------
|
||||
// ----------------------------------------------
|
||||
function StompServerNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = n.server;
|
||||
this.port = n.port;
|
||||
this.protocolversion = n.protocolversion;
|
||||
this.vhost = n.vhost;
|
||||
this.reconnectretries = n.reconnectretries || 999999;
|
||||
this.reconnectdelay = (n.reconnectdelay || 15) * 1000;
|
||||
this.name = n.name;
|
||||
this.username = this.credentials.user;
|
||||
this.password = this.credentials.password;
|
||||
const node = this;
|
||||
// To keep track of processing nodes that use this config node for their connection
|
||||
node.users = {};
|
||||
// Config node state
|
||||
node.connected = false;
|
||||
node.connecting = false;
|
||||
/** Flag to avoid race conditions between `deregister` and the `close` event of the config node (ex. on redeploy) */
|
||||
node.closing = false;
|
||||
/** Options to pass to the stomp-client API */
|
||||
node.options = {};
|
||||
node.sessionId = null;
|
||||
node.subscribtionIndex = 1;
|
||||
node.subscriptionIds = {};
|
||||
/** @type { StompClient } */
|
||||
node.client;
|
||||
node.setOptions = function(options, init) {
|
||||
if (!options || typeof options !== "object") {
|
||||
return; // Nothing to change
|
||||
}
|
||||
|
||||
// Apply property changes (only if the property exists in the options object)
|
||||
setIfHasProperty(options, node, "server", init);
|
||||
setIfHasProperty(options, node, "port", init);
|
||||
setIfHasProperty(options, node, "protocolversion", init);
|
||||
setIfHasProperty(options, node, "vhost", init);
|
||||
setIfHasProperty(options, node, "reconnectretries", init);
|
||||
setIfHasProperty(options, node, "reconnectdelay", init);
|
||||
|
||||
if (node.credentials) {
|
||||
node.username = node.credentials.username;
|
||||
node.password = node.credentials.password;
|
||||
}
|
||||
if (!init && hasProperty(options, "username")) {
|
||||
node.username = options.username;
|
||||
}
|
||||
if (!init && hasProperty(options, "password")) {
|
||||
node.password = options.password;
|
||||
}
|
||||
|
||||
// Build options for passing to the stomp-client API
|
||||
node.options = {
|
||||
address: node.server,
|
||||
port: node.port * 1,
|
||||
user: node.username,
|
||||
pass: node.password,
|
||||
protocolVersion: node.protocolversion,
|
||||
reconnectOpts: {
|
||||
retries: node.reconnectretries * 1,
|
||||
delay: node.reconnectdelay * 1000
|
||||
},
|
||||
vhost: node.vhost
|
||||
};
|
||||
}
|
||||
|
||||
node.setOptions(n, true);
|
||||
|
||||
/**
|
||||
* Register a STOMP processing node to the connection.
|
||||
* @param { StompInNode | StompOutNode | StompAckNode } stompNode The STOMP processing node to register
|
||||
* @param { Function } callback
|
||||
*/
|
||||
node.register = function(stompNode, callback = () => {}) {
|
||||
node.users[stompNode.id] = stompNode;
|
||||
// Auto connect when first STOMP processing node is added
|
||||
if (Object.keys(node.users).length === 1) {
|
||||
node.connect(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove registered STOMP processing nodes from the connection.
|
||||
* @param { StompInNode | StompOutNode | StompAckNode } stompNode The STOMP processing node to unregister
|
||||
* @param { Boolean } autoDisconnect Automatically disconnect from the STOM server when no processing nodes registered to the connection
|
||||
* @param { Function } callback
|
||||
*/
|
||||
node.deregister = function(stompNode, autoDisconnect, callback = () => {}) {
|
||||
delete node.users[stompNode.id];
|
||||
if (autoDisconnect && !node.closing && node.connected && Object.keys(node.users).length === 0) {
|
||||
node.disconnect(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether a new connection can be made.
|
||||
* @returns `true` or `false`
|
||||
*/
|
||||
node.canConnect = function() {
|
||||
return !node.connected && !node.connecting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the STOMP server.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
node.connect = function(callback = () => {}) {
|
||||
if (node.canConnect()) {
|
||||
node.closing = false;
|
||||
node.connecting = true;
|
||||
setStatusConnecting(node, true);
|
||||
|
||||
try {
|
||||
// Remove left over client if needed
|
||||
if (node.client) {
|
||||
node.client.disconnect();
|
||||
node.client = null;
|
||||
}
|
||||
|
||||
node.client = new StompClient(node.options);
|
||||
|
||||
node.client.on("connect", function() {
|
||||
node.closing = false;
|
||||
node.connecting = false;
|
||||
node.connected = true;
|
||||
callback();
|
||||
|
||||
node.log("Connected to STOMP server", {sessionId: node.sessionId, url: `${node.options.address}:${node.options.port}`, protocolVersion: node.options.protocolVersion});
|
||||
setStatusConnected(node, true);
|
||||
});
|
||||
|
||||
node.client.on("reconnect", function(sessionId, numOfRetries) {
|
||||
node.closing = false;
|
||||
node.connecting = false;
|
||||
node.connected = true;
|
||||
node.sessionId = sessionId;
|
||||
callback();
|
||||
|
||||
node.log("Reconnected to STOMP server", {sessionId: node.sessionId, url: `${node.options.address}:${node.options.port}`, protocolVersion: node.options.protocolVersion, retries: numOfRetries});
|
||||
setStatusConnected(node, true);
|
||||
});
|
||||
|
||||
node.client.on("reconnecting", function() {
|
||||
node.warn("reconnecting");
|
||||
node.connecting = true;
|
||||
node.connected = false;
|
||||
|
||||
node.log("Reconnecting to STOMP server...", {url: `${node.options.address}:${node.options.port}`, protocolVersion: node.options.protocolVersion});
|
||||
setStatusConnecting(node, true);
|
||||
});
|
||||
|
||||
node.client.on("error", function(err) {
|
||||
node.error(err);
|
||||
setStatusError(node, true);
|
||||
});
|
||||
|
||||
node.client.connect(function(sessionId) {
|
||||
node.sessionId = sessionId;
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
} else {
|
||||
node.log("Not connecting to STOMP server, already connected");
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the STOMP server.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
node.disconnect = function(callback = () => {}) {
|
||||
const waitDisconnect = (client, timeout) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Set flag to avoid race conditions for disconnect as every node tries to call it directly or indirectly using deregister
|
||||
node.closing = true;
|
||||
const t = setTimeout(() => {
|
||||
reject();
|
||||
}, timeout);
|
||||
client.disconnect(() => {
|
||||
clearTimeout(t);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!node.client) {
|
||||
node.warn("Can't disconnect, connection not initialized.");
|
||||
callback();
|
||||
} else if (node.closing || !node.connected) {
|
||||
// Disconnection already in progress or not connected
|
||||
callback();
|
||||
} else {
|
||||
node.log("Unsubscribing from STOMP queue's...");
|
||||
const subscribedQueues = Object.keys(node.subscriptionIds);
|
||||
subscribedQueues.forEach(function(queue) {
|
||||
node.unsubscribe(queue);
|
||||
});
|
||||
node.log('Disconnecting from STOMP server...');
|
||||
waitDisconnect(node.client, 2000).then(() => {
|
||||
node.log("Disconnected from STOMP server", {sessionId: node.sessionId, url: `${node.options.address}:${node.options.port}`, protocolVersion: node.options.protocolVersion})
|
||||
}).catch(() => {
|
||||
node.log("Disconnect timeout closing node...");
|
||||
}).finally(() => {
|
||||
node.sessionId = null;
|
||||
node.subscribtionIndex = 1;
|
||||
node.subscriptionIds = {};
|
||||
node.connected = false;
|
||||
node.connecting = false;
|
||||
setStatusDisconnected(node, true);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a given STOMP queue.
|
||||
* @param { String} queue The queue to subscribe to
|
||||
* @param { "auto" | "client" | "client-individual" } clientAck Can be `auto`, `client` or `client-individual` (the latter only starting from STOMP v1.1)
|
||||
* @param { Function } callback
|
||||
*/
|
||||
node.subscribe = function(queue, acknowledgment, callback) {
|
||||
node.log(`Subscribing to: ${queue}`);
|
||||
|
||||
if (node.connected && !node.closing) {
|
||||
if (!node.subscriptionIds[queue]) {
|
||||
node.subscriptionIds[queue] = node.subscribtionIndex++;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
id: node.subscriptionIds[queue],
|
||||
// Only set client-individual if not v1.0
|
||||
ack: acknowledgment === "client-individual" && node.options.protocolVersion === "1.0" ? "client" : acknowledgment
|
||||
}
|
||||
|
||||
node.client.subscribe(queue, headers, function(body, responseHeaders) {
|
||||
let msg = { headers: responseHeaders, topic: queue };
|
||||
try {
|
||||
msg.payload = JSON.parse(body);
|
||||
} catch {
|
||||
msg.payload = body;
|
||||
}
|
||||
callback(msg);
|
||||
});
|
||||
} else {
|
||||
node.error("Can't subscribe, not connected");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from a STOMP queue.
|
||||
* @param {String} queue The STOMP queue to unsubscribe from
|
||||
* @param {Object} headers Headers to add to the unsubscribe message
|
||||
*/
|
||||
node.unsubscribe = function(queue, headers = {}) {
|
||||
delete node.subscriptionIds[queue];
|
||||
if (node.connected && !node.closing) {
|
||||
node.client.unsubscribe(queue, headers);
|
||||
node.log(`Unsubscribed from ${queue}`, headers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a STOMP message on a queue.
|
||||
* @param {String} queue The STOMP queue to publish to
|
||||
* @param {any} message The message to send
|
||||
* @param {Object} headers STOMP headers to add to the SEND command
|
||||
*/
|
||||
node.publish = function(queue, message, headers = {}) {
|
||||
if (node.connected && !node.closing) {
|
||||
node.client.publish(queue, message, headers);
|
||||
} else {
|
||||
node.error("Can't publish, not connected");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledge (a) message(s) that was received from the specified queue.
|
||||
* @param {String} queue The queue/topic to send an acknowledgment for
|
||||
* @param {String} messageId ID of the message that was received from the server, which can be found in the reponse header as `message-id`
|
||||
* @param {String} transaction Optional transaction name
|
||||
*/
|
||||
node.ack = function(queue, messageId, transaction = undefined) {
|
||||
if (node.connected && !node.closing) {
|
||||
node.client.ack(messageId, node.subscriptionIds[queue], transaction);
|
||||
} else {
|
||||
node.error("Can't send acknowledgment, not connected");
|
||||
}
|
||||
}
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.disconnect(function() { done (); });
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("stomp-server",StompServerNode,{
|
||||
RED.nodes.registerType("stomp-server", StompServerNode, {
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
password: {type: "password"}
|
||||
user: { type: "text" },
|
||||
password: { type: "password" }
|
||||
}
|
||||
});
|
||||
|
||||
function StompInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = n.server;
|
||||
this.topic = n.topic;
|
||||
/** @type { StompInNode } */
|
||||
const node = this;
|
||||
node.server = n.server;
|
||||
/** @type { StompServerNode } */
|
||||
node.serverConnection = RED.nodes.getNode(node.server);
|
||||
node.topic = n.topic;
|
||||
node.ack = n.ack;
|
||||
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
this.stompClientOpts = {
|
||||
address: this.serverConfig.server,
|
||||
port: this.serverConfig.port * 1,
|
||||
user: this.serverConfig.username,
|
||||
pass: this.serverConfig.password,
|
||||
protocolVersion: this.serverConfig.protocolversion,
|
||||
reconnectOpts: {
|
||||
retries: this.serverConfig.reconnectretries * 1,
|
||||
delay: this.serverConfig.reconnectdelay * 1
|
||||
if (node.serverConnection) {
|
||||
setStatusDisconnected(node);
|
||||
|
||||
if (node.topic) {
|
||||
node.serverConnection.register(node, function() {
|
||||
node.serverConnection.subscribe(node.topic, node.ack, function(msg) {
|
||||
node.send(msg);
|
||||
});
|
||||
});
|
||||
|
||||
if (node.serverConnection.connected) {
|
||||
setStatusConnected(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this.serverConfig.vhost) {
|
||||
this.stompClientOpts.vhost = this.serverConfig.vhost;
|
||||
} else {
|
||||
node.error("Missing server config");
|
||||
}
|
||||
|
||||
var node = this;
|
||||
var msg = {topic:this.topic};
|
||||
node.client = new StompClient(node.stompClientOpts);
|
||||
|
||||
node.client.on("connect", function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
|
||||
node.client.on("reconnecting", function() {
|
||||
node.status({fill:"red",shape:"ring",text:"reconnecting"});
|
||||
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);
|
||||
});
|
||||
|
||||
node.status({fill:"grey",shape:"ring",text:"connecting"});
|
||||
node.client.connect(function(sessionId) {
|
||||
node.log('subscribing to: '+node.topic);
|
||||
node.client.subscribe(node.topic, function(body, headers) {
|
||||
var newmsg={"headers":headers,"topic":node.topic}
|
||||
try {
|
||||
newmsg.payload = JSON.parse(body);
|
||||
}
|
||||
catch(e) {
|
||||
newmsg.payload = body;
|
||||
}
|
||||
node.send(newmsg);
|
||||
});
|
||||
}, function(error) {
|
||||
node.status({fill:"grey",shape:"dot",text:"error"});
|
||||
node.warn(error);
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
if (node.client) {
|
||||
// disconnect can accept a callback - but it is not always called.
|
||||
node.client.disconnect();
|
||||
node.on("close", function(removed, done) {
|
||||
if (node.serverConnection) {
|
||||
node.serverConnection.unsubscribe(node.topic);
|
||||
node.serverConnection.deregister(node, true, done);
|
||||
node.serverConnection = null;
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("stomp in",StompInNode);
|
||||
@ -97,65 +409,114 @@ module.exports = function(RED) {
|
||||
|
||||
function StompOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = n.server;
|
||||
this.topic = n.topic;
|
||||
/** @type { StompOutNode } */
|
||||
const node = this;
|
||||
node.server = n.server;
|
||||
/** @type { StompServerNode } */
|
||||
node.serverConnection = RED.nodes.getNode(node.server);
|
||||
node.topic = n.topic;
|
||||
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
this.stompClientOpts = {
|
||||
address: this.serverConfig.server,
|
||||
port: this.serverConfig.port * 1,
|
||||
user: this.serverConfig.username,
|
||||
pass: this.serverConfig.password,
|
||||
protocolVersion: this.serverConfig.protocolversion,
|
||||
reconnectOpts: {
|
||||
retries: this.serverConfig.reconnectretries * 1,
|
||||
delay: this.serverConfig.reconnectdelay * 1
|
||||
if (node.serverConnection) {
|
||||
setStatusDisconnected(node);
|
||||
|
||||
node.on("input", function(msg, send, done) {
|
||||
if (node.topic && msg.payload) {
|
||||
try {
|
||||
msg.payload = JSON.stringify(msg.payload);
|
||||
} catch {
|
||||
msg.payload = `${msg.payload}`;
|
||||
}
|
||||
node.serverConnection.publish(node.topic, msg.payload, msg.headers || {});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
node.serverConnection.register(node);
|
||||
if (node.serverConnection.connected) {
|
||||
setStatusConnected(node);
|
||||
}
|
||||
};
|
||||
if (this.serverConfig.vhost) {
|
||||
this.stompClientOpts.vhost = this.serverConfig.vhost;
|
||||
} else {
|
||||
node.error("Missing server config");
|
||||
}
|
||||
|
||||
var node = this;
|
||||
node.client = new StompClient(node.stompClientOpts);
|
||||
|
||||
node.client.on("connect", function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
|
||||
node.client.on("reconnecting", function() {
|
||||
node.status({fill:"red",shape:"ring",text:"reconnecting"});
|
||||
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);
|
||||
});
|
||||
|
||||
node.status({fill:"grey",shape:"ring",text:"connecting"});
|
||||
node.client.connect(function(sessionId) {
|
||||
}, function(error) {
|
||||
node.status({fill:"grey",shape:"dot",text:"error"});
|
||||
node.warn(error);
|
||||
});
|
||||
|
||||
node.on("input", function(msg) {
|
||||
node.client.publish(node.topic || msg.topic, msg.payload, msg.headers);
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
if (node.client) {
|
||||
// disconnect can accept a callback - but it is not always called.
|
||||
node.client.disconnect();
|
||||
node.on("close", function(removed, done) {
|
||||
if (node.serverConnection) {
|
||||
node.serverConnection.deregister(node, true, done);
|
||||
node.serverConnection = null;
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("stomp out",StompOutNode);
|
||||
|
||||
function StompAckNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
/** @type { StompOutNode } */
|
||||
const node = this;
|
||||
node.server = n.server;
|
||||
/** @type { StompServerNode } */
|
||||
node.serverConnection = RED.nodes.getNode(node.server);
|
||||
node.topic = n.topic;
|
||||
|
||||
if (node.serverConnection) {
|
||||
setStatusDisconnected(node);
|
||||
|
||||
node.on("input", function(msg, send, done) {
|
||||
node.serverConnection.ack(node.topic, msg.messageId, msg.transaction);
|
||||
done();
|
||||
});
|
||||
|
||||
node.serverConnection.register(node);
|
||||
if (node.serverConnection.connected) {
|
||||
setStatusConnected(node);
|
||||
}
|
||||
} else {
|
||||
node.error("Missing server config");
|
||||
}
|
||||
|
||||
node.on("close", function(removed, done) {
|
||||
if (node.serverConnection) {
|
||||
node.serverConnection.deregister(node, true, done);
|
||||
node.serverConnection = null;
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("stomp ack",StompAckNode);
|
||||
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// ----------------- Helpers --------------------
|
||||
// ----------------------------------------------
|
||||
/**
|
||||
* Helper function for applying changes to an objects properties ONLY when the src object actually has the property.
|
||||
* This avoids setting a `dst` property null/undefined when the `src` object doesnt have the named property.
|
||||
* @param {object} src Source object containing properties
|
||||
* @param {object} dst Destination object to set property
|
||||
* @param {string} propName The property name to set in the Destination object
|
||||
* @param {boolean} force force the dst property to be updated/created even if src property is empty
|
||||
*/
|
||||
function setIfHasProperty(src, dst, propName, force) {
|
||||
if (src && dst && propName) {
|
||||
const ok = force || hasProperty(src, propName);
|
||||
if (ok) {
|
||||
dst[propName] = src[propName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test an object has a property
|
||||
* @param {object} obj Object to test
|
||||
* @param {string} propName Name of property to find
|
||||
* @returns true if object has property `propName`
|
||||
*/
|
||||
function hasProperty(obj, propName) {
|
||||
//JavaScript does not protect the property name hasOwnProperty
|
||||
//Object.prototype.hasOwnProperty.call is the recommended/safer test
|
||||
return Object.prototype.hasOwnProperty.call(obj, propName);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red-node-stomp",
|
||||
"version" : "0.0.14",
|
||||
"version" : "1.0.0",
|
||||
"description" : "A Node-RED node to publish and subscribe to/from a Stomp server",
|
||||
"dependencies" : {
|
||||
"stomp-client" : "^0.9.0"
|
||||
|
@ -44,6 +44,7 @@
|
||||
<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">
|
||||
@ -64,7 +65,7 @@
|
||||
<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>
|
||||
@ -74,22 +75,6 @@
|
||||
<input type="text" id="node-input-dname" data-i18n="[placeholder]node-red:common.label.name">
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip"><span data-i18n="[html]email.tip.cred"></span></div>
|
||||
<script>
|
||||
$("#node-input-authtype").change(function() {
|
||||
var protocol = $("#node-input-authtype").val();
|
||||
if (protocol === "BASIC") {
|
||||
$(".node-input-password").show();
|
||||
$(".node-input-saslformat").hide();
|
||||
$(".node-input-token").hide();
|
||||
} else {
|
||||
$(".node-input-password").hide();
|
||||
$(".node-input-saslformat").show();
|
||||
$(".node-input-token").show();
|
||||
$("#node-input-fetch").val("trigger");
|
||||
$("#node-input-fetch").change();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -131,6 +116,30 @@
|
||||
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 {
|
||||
@ -190,13 +199,14 @@
|
||||
<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">
|
||||
<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 node-input-password">
|
||||
<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>
|
||||
@ -284,10 +294,18 @@
|
||||
$("#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 {
|
||||
} 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();
|
||||
|
@ -27,12 +27,6 @@ module.exports = function(RED) {
|
||||
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;
|
||||
@ -41,7 +35,6 @@ 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;
|
||||
@ -49,36 +42,26 @@ module.exports = function(RED) {
|
||||
}
|
||||
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"));
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if (globalkeys) {
|
||||
this.password = globalkeys.pass;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error(RED._("email.errors.nopassword"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
this.error(RED._("email.errors.nopassword"));
|
||||
}
|
||||
}
|
||||
else if (this.authtype === "XOAUTH2") {
|
||||
this.saslformat = n.saslformat;
|
||||
if(n.token!=="") {
|
||||
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;
|
||||
|
||||
@ -94,10 +77,11 @@ module.exports = function(RED) {
|
||||
user: node.userid,
|
||||
pass: node.password
|
||||
};
|
||||
} else if(node.authtype == "XOAUTH2") {
|
||||
}
|
||||
else if (node.authtype === "XOAUTH2") {
|
||||
var value = RED.util.getMessageProperty(msg,node.token);
|
||||
if (value !== undefined) {
|
||||
if(node.saslformat) {
|
||||
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 {
|
||||
@ -226,8 +210,8 @@ 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;
|
||||
@ -240,40 +224,28 @@ module.exports = function(RED) {
|
||||
this.repeat = 0;
|
||||
}
|
||||
|
||||
var flag = false;
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if (globalkeys) {
|
||||
this.password = globalkeys.pass;
|
||||
flag = true;
|
||||
} else {
|
||||
this.error(RED._("email.errors.nopassword"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
this.error(RED._("email.errors.nopassword"));
|
||||
}
|
||||
}
|
||||
else if (this.authtype === "XOAUTH2") {
|
||||
this.saslformat = n.saslformat;
|
||||
if(n.token!=="") {
|
||||
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});
|
||||
}
|
||||
|
||||
var node = this;
|
||||
node.interval_id = null;
|
||||
@ -321,7 +293,7 @@ module.exports = function(RED) {
|
||||
try {
|
||||
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"});
|
||||
await pop3.connect();
|
||||
if (node.authtype == "XOAUTH2") {
|
||||
if (node.authtype === "XOAUTH2") {
|
||||
var value = RED.util.getMessageProperty(msg,node.token);
|
||||
if (value !== undefined) {
|
||||
if (node.saslformat) {
|
||||
@ -334,7 +306,7 @@ module.exports = function(RED) {
|
||||
await pop3.command('AUTH', "XOAUTH2");
|
||||
await pop3.command(saslxoauth2);
|
||||
|
||||
} else if (node.authtype == "BASIC") {
|
||||
} else if (node.authtype === "BASIC") {
|
||||
await pop3.command('USER', node.userid);
|
||||
await pop3.command('PASS', node.password);
|
||||
}
|
||||
@ -401,7 +373,7 @@ module.exports = function(RED) {
|
||||
function checkIMAP(msg,send,done) {
|
||||
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
|
||||
var saslxoauth2 = "";
|
||||
if (node.authtype == "XOAUTH2") {
|
||||
if (node.authtype === "XOAUTH2") {
|
||||
var value = RED.util.getMessageProperty(msg,node.token);
|
||||
if (value !== undefined) {
|
||||
if (node.saslformat) {
|
||||
|
@ -11,7 +11,10 @@ getting an application password if you have two-factor authentication enabled.
|
||||
|
||||
For Exchange and Outlook 365 you must use OAuth2.0.
|
||||
|
||||
**Note :** Version 1.x of this node requires **Node.js v8** or newer.
|
||||
**Notes **:
|
||||
Version 2.x of this node requires **Node.js v14** or newer.
|
||||
Version 1.91 of this node required **Node.js v14** or newer.
|
||||
Previous versions of this node required **Node.js v8** or newer.
|
||||
|
||||
Install
|
||||
-------
|
||||
@ -29,12 +32,11 @@ If you are accessing GMail you may need to either enable <a target="_new" href="
|
||||
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
|
||||
-----
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "node-red-node-email",
|
||||
"version": "1.19.1",
|
||||
"version": "2.0.1",
|
||||
"description": "Node-RED nodes to send and receive simple emails.",
|
||||
"dependencies": {
|
||||
"imap": "^0.8.19",
|
||||
"node-pop3": "^0.8.0",
|
||||
"mailparser": "^3.6.4",
|
||||
"nodemailer": "^6.9.1",
|
||||
"smtp-server": "^3.11.0"
|
||||
"nodemailer": "^6.9.3",
|
||||
"smtp-server": "^3.12.0"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"imap",
|
||||
@ -40,10 +40,10 @@
|
||||
},
|
||||
"author": {
|
||||
"name": "Dave Conway-Jones",
|
||||
"email": "ceejay@vnet.ibm.com",
|
||||
"email": "dceejay@gmail.com",
|
||||
"url": "http://nodered.org"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name" : "node-red-node-timeswitch",
|
||||
"version" : "1.0.0",
|
||||
"version" : "1.1.1",
|
||||
"description" : "A Node-RED node to provide a simple timeswitch to schedule daily on/off events.",
|
||||
"dependencies" : {
|
||||
"spacetime": "^7.4.0",
|
||||
"suncalc": "^1.8.0"
|
||||
"spacetime": "^7.4.3",
|
||||
"suncalc": "^1.9.0"
|
||||
},
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
|
@ -46,8 +46,19 @@ module.exports = function (RED) {
|
||||
|
||||
// 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");
|
||||
const sunAlt = SunCalc.getPosition(nowNative, node.lat, node.lon).altitude;
|
||||
var sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute");
|
||||
var sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute");
|
||||
|
||||
if (sunEvents[SUNRISE_KEY] == "Invalid Date") {
|
||||
if (sunAlt >= 0) { sunriseDateTime = now.startOf("day"); }
|
||||
else { sunriseDateTime = now.endOf("day"); }
|
||||
}
|
||||
|
||||
if (sunEvents[SUNSET_KEY] == "Invalid Date") {
|
||||
if (sunAlt >= 0) { sunsetDateTime = now.endOf("day"); }
|
||||
else { sunsetDateTime = now.startOf("day"); }
|
||||
}
|
||||
|
||||
// add optional sun event offset, if specified
|
||||
sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes");
|
||||
@ -56,13 +67,15 @@ module.exports = function (RED) {
|
||||
// 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");
|
||||
// sunriseDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNRISE_KEY]).nearest("minute");
|
||||
sunriseDateTime = sunriseDateTime.add(1, "day");
|
||||
// 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");
|
||||
// sunsetDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNSET_KEY]).nearest("minute");
|
||||
sunsetDateTime = sunsetDateTime.add(1, "day");
|
||||
// add optional sun event offset, if specified (again)
|
||||
sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes");
|
||||
}
|
||||
@ -126,19 +139,36 @@ module.exports = function (RED) {
|
||||
// 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")}`
|
||||
});
|
||||
if (nextTime) {
|
||||
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 {
|
||||
node.status({
|
||||
fill: "blue",
|
||||
shape: "dot",
|
||||
text: `off until ${nextTime.goto(selectedTimeZone.name).format("time-24")}`
|
||||
});
|
||||
if (payload == 1) {
|
||||
node.status({
|
||||
fill: "yellow",
|
||||
shape: "dot",
|
||||
text: `on`
|
||||
});
|
||||
} else {
|
||||
node.status({
|
||||
fill: "blue",
|
||||
shape: "dot",
|
||||
text: `off`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var msg = {};
|
||||
if (node.mytopic) {
|
||||
msg.topic = node.mytopic;
|
||||
|
Loading…
Reference in New Issue
Block a user