From 612ff93161ef1c6113c0cd03c1eae634e744e44d Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Thu, 25 May 2023 08:59:05 +0000 Subject: [PATCH 01/20] Bump serial port (#999) * Bump serial port dependency to 11.0.0 This "should" fix the broken binaries on Arm/Alpine (e.g. docker) --- io/serialport/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io/serialport/package.json b/io/serialport/package.json index 32ebcdc5..d4d30da9 100644 --- a/io/serialport/package.json +++ b/io/serialport/package.json @@ -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", From 72f7f5179adb0bf17a8a9edf3a378d42b983d12d Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 25 May 2023 22:53:11 +0900 Subject: [PATCH 02/20] Fix flow examples of Sense HAT simulator nodes (#991) * Add indents to flow examples of Sense HAT simulator * Fix flow examples of Sense HAT simulator nodes --- hardware/sensehatsim/examples/Clock.json | 57 +++++++++++++++++++++- hardware/sensehatsim/examples/Compass.json | 54 +++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/hardware/sensehatsim/examples/Clock.json b/hardware/sensehatsim/examples/Clock.json index 54525109..a067aab6 100644 --- a/hardware/sensehatsim/examples/Clock.json +++ b/hardware/sensehatsim/examples/Clock.json @@ -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 Date: Thu, 25 May 2023 14:56:03 +0100 Subject: [PATCH 03/20] Bump Sensehat for multi read and examples --- hardware/sensehat/package.json | 2 +- hardware/sensehat/sensehat.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hardware/sensehat/package.json b/hardware/sensehat/package.json index c9845b99..d2805955 100644 --- a/hardware/sensehat/package.json +++ b/hardware/sensehat/package.json @@ -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", diff --git a/hardware/sensehat/sensehat.py b/hardware/sensehat/sensehat.py index 2b53248b..f5a82deb 100644 --- a/hardware/sensehat/sensehat.py +++ b/hardware/sensehat/sensehat.py @@ -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 From ddaa9b24b8f09784d5f504d308cd015fafc09f1e Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sat, 27 May 2023 19:40:19 +0100 Subject: [PATCH 04/20] let timeswitch handle high latitudes better to close #1002 --- time/timeswitch/package.json | 6 ++-- time/timeswitch/timeswitch.js | 56 ++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/time/timeswitch/package.json b/time/timeswitch/package.json index 71917d79..52cdad25 100644 --- a/time/timeswitch/package.json +++ b/time/timeswitch/package.json @@ -1,10 +1,10 @@ { "name" : "node-red-node-timeswitch", - "version" : "1.0.0", + "version" : "1.1.0", "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", diff --git a/time/timeswitch/timeswitch.js b/time/timeswitch/timeswitch.js index fc5e3c87..731a3642 100644 --- a/time/timeswitch/timeswitch.js +++ b/time/timeswitch/timeswitch.js @@ -46,9 +46,16 @@ module.exports = function (RED) { // all sun events for the given lat/long const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon); + const sunAlt = SunCalc.getPosition(nowNative, node.lat, node.lon).altitude; let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute"); let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute"); + var aday + if (sunEvents[SUNRISE_KEY] == "Invalid Date" || sunEvents[SUNSET_KEY] == "Invalid Date") { + if (sunAlt >= 0) { aday = 1; } + else { aday = 0; } + } + // add optional sun event offset, if specified sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes"); sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes"); @@ -126,19 +133,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; @@ -186,6 +210,16 @@ module.exports = function (RED) { return; } + if (proceed && aday === 1) { + sendPayload(1); + return; + } + + if (proceed && aday === 0) { + sendPayload(0); + return; + } + // if the chronological order is NOW --> ON --> OFF, then now should be OFF if (proceed && selectedOffTime.isAfter(selectedOnTime)) { sendPayload(0, selectedOnTime); From 0342a4b7ae23eb4270e53c1ab30cc60cef167737 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sun, 28 May 2023 09:58:12 +0100 Subject: [PATCH 05/20] better fix for handling of high latitudes to close #1002 again --- time/timeswitch/package.json | 2 +- time/timeswitch/timeswitch.js | 32 ++++++++++++++------------------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/time/timeswitch/package.json b/time/timeswitch/package.json index 52cdad25..1c0b3de5 100644 --- a/time/timeswitch/package.json +++ b/time/timeswitch/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-timeswitch", - "version" : "1.1.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.3", diff --git a/time/timeswitch/timeswitch.js b/time/timeswitch/timeswitch.js index 731a3642..744fca44 100644 --- a/time/timeswitch/timeswitch.js +++ b/time/timeswitch/timeswitch.js @@ -47,13 +47,17 @@ module.exports = function (RED) { // all sun events for the given lat/long const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon); const sunAlt = SunCalc.getPosition(nowNative, node.lat, node.lon).altitude; - let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute"); - let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute"); + var sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute"); + var sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute"); - var aday - if (sunEvents[SUNRISE_KEY] == "Invalid Date" || sunEvents[SUNSET_KEY] == "Invalid Date") { - if (sunAlt >= 0) { aday = 1; } - else { aday = 0; } + 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 @@ -63,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"); } @@ -210,16 +216,6 @@ module.exports = function (RED) { return; } - if (proceed && aday === 1) { - sendPayload(1); - return; - } - - if (proceed && aday === 0) { - sendPayload(0); - return; - } - // if the chronological order is NOW --> ON --> OFF, then now should be OFF if (proceed && selectedOffTime.isAfter(selectedOnTime)) { sendPayload(0, selectedOnTime); From 490a3d8d37d0a5fe93bd26800020e86f349bc177 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 29 May 2023 21:59:40 +0100 Subject: [PATCH 06/20] Fix up readme - and bump deps to close #1003 --- social/email/README.md | 5 +++-- social/email/package.json | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/social/email/README.md b/social/email/README.md index bac85577..3d6ee45b 100644 --- a/social/email/README.md +++ b/social/email/README.md @@ -12,8 +12,9 @@ getting an application password if you have two-factor authentication enabled. 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. + 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 ------- diff --git a/social/email/package.json b/social/email/package.json index 2d98b4c6..e94aec55 100644 --- a/social/email/package.json +++ b/social/email/package.json @@ -1,13 +1,13 @@ { "name": "node-red-node-email", - "version": "2.0.0", + "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" } } From 0d7f0cb16d5acb32565dd568669d05dfbbe38226 Mon Sep 17 00:00:00 2001 From: Olivier Verhaegen <56387556+OlivierVerhaegen@users.noreply.github.com> Date: Tue, 30 May 2023 12:05:25 +0200 Subject: [PATCH 07/20] STOMP refactor in accordance with MQTT (shared connection using config node) + client ACK (#988) * Bugfix: show connected state after reconnect * Bugfix: show connected state for stomp out node after reconnect * Add support for ACK messages * Add optional subscription id * Typo fix * Subscription ID not required * Bugfix "node.ack is not a fuction" * Use shared client connection * Bugfix shared connection * Improvements & bugfixes to shared connection * Bugfix connecting state * Set connected state in connect callback * Typo fix * add server shared connection variables * Bugfix for shared state * WIP * Complete refactor based on MQTT nodes to be able to share server connection between nodes * Change address back to server for backwards compatibility * Fixes for race conditions on node closing * Add disconnect timeout of 2s to avoid "Error stopping node" * If not connected, do not try to disconnect * Fix for disconnecting log * Styling fix for ack select form row * Typo fixes * Typo fix * Bugfix: subscription before connected * Bugfix: stringify payload before sending to be able to send numbers etc * Bugfix: not saving ack field * Bugfix: ack * Bugfix: ack * Bugfix: ack & better docs regardign ack * BugFix: reconnect delay * Improvements regarding cleanup on close * Handle connect and reconnect event in the same way * Typo fix * Fix backwards compatibility --- io/stomp/18-stomp.html | 70 ++++- io/stomp/18-stomp.js | 611 ++++++++++++++++++++++++++++++++--------- 2 files changed, 554 insertions(+), 127 deletions(-) diff --git a/io/stomp/18-stomp.html b/io/stomp/18-stomp.html index e643807c..aa847128 100644 --- a/io/stomp/18-stomp.html +++ b/io/stomp/18-stomp.html @@ -8,6 +8,18 @@ +
+ + +
+
+ Enabling the ACK (acknowledgment) will set the ack header to client 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 ack node. +
@@ -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 @@ } }); + + + + + + diff --git a/io/stomp/18-stomp.js b/io/stomp/18-stomp.js index a35da1a7..8ff77156 100644 --- a/io/stomp/18-stomp.js +++ b/io/stomp/18-stomp.js @@ -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); + } \ No newline at end of file From 71a39ac16be869f6369d02f6545847455d0cb6c6 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 30 May 2023 11:07:57 +0100 Subject: [PATCH 08/20] Bump stomp node for PR #988 --- io/stomp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/stomp/package.json b/io/stomp/package.json index 1f1d1329..00f51038 100644 --- a/io/stomp/package.json +++ b/io/stomp/package.json @@ -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" From 3fd14223efe584893d6f7f35b6a9a67cb656adc6 Mon Sep 17 00:00:00 2001 From: Olivier Verhaegen <56387556+OlivierVerhaegen@users.noreply.github.com> Date: Tue, 6 Jun 2023 12:25:06 +0200 Subject: [PATCH 09/20] Bugfix: use of credentials in STOMP server (username property typo) (#1007) * Bugfix: use of credentials in stomp server (username property typo) --- io/stomp/18-stomp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/stomp/18-stomp.js b/io/stomp/18-stomp.js index 8ff77156..8819f857 100644 --- a/io/stomp/18-stomp.js +++ b/io/stomp/18-stomp.js @@ -100,7 +100,7 @@ module.exports = function(RED) { setIfHasProperty(options, node, "reconnectdelay", init); if (node.credentials) { - node.username = node.credentials.username; + node.username = node.credentials.user; node.password = node.credentials.password; } if (!init && hasProperty(options, "username")) { From 983cab970e133e08443c029ca22f5bfc46b60b84 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 6 Jun 2023 11:26:25 +0100 Subject: [PATCH 10/20] bump for username credential fix to close #1006 --- io/stomp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/stomp/package.json b/io/stomp/package.json index 00f51038..fd3c0eff 100644 --- a/io/stomp/package.json +++ b/io/stomp/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-stomp", - "version" : "1.0.0", + "version" : "1.0.1", "description" : "A Node-RED node to publish and subscribe to/from a Stomp server", "dependencies" : { "stomp-client" : "^0.9.0" From 8743c2afa63c404a605cb9cb61d8027f86eddc8d Mon Sep 17 00:00:00 2001 From: Olivier Verhaegen <56387556+OlivierVerhaegen@users.noreply.github.com> Date: Thu, 8 Jun 2023 22:14:40 +0200 Subject: [PATCH 11/20] STOMP bugfix: msg.topic can't be used with STOMP out node and ack node (#1009) * Bugfix: not responsive to msg.topic on input of STOMP out node * Bugfix: not responsive to msg.topic on input of STOMP ack and out node * Bugfix: backward compatible node.topic has prio + Docs: added info about msg.topic in ACK node docs * Typo fix --- io/stomp/18-stomp.html | 1 + io/stomp/18-stomp.js | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/io/stomp/18-stomp.html b/io/stomp/18-stomp.html index aa847128..4f2549a9 100644 --- a/io/stomp/18-stomp.html +++ b/io/stomp/18-stomp.html @@ -197,6 +197,7 @@

The node allows for following inputs:

    +
  • msg.topicThe topic for which the message should be acknowledged, if the Destination field is set the msg.topic will be overwritten by this value
  • msg.messageId: The id of the message to acknowledge
  • msg.transaction: Optional transaction name
diff --git a/io/stomp/18-stomp.js b/io/stomp/18-stomp.js index 8819f857..a28186aa 100644 --- a/io/stomp/18-stomp.js +++ b/io/stomp/18-stomp.js @@ -420,15 +420,21 @@ module.exports = function(RED) { setStatusDisconnected(node); node.on("input", function(msg, send, done) { - if (node.topic && msg.payload) { + const topic = node.topic || msg.topic; + if (topic.length > 0 && 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.publish(topic, msg.payload, msg.headers || {}); + } else if (!topic.length > 0) { + node.warn('No valid publish topic'); + + } else { + node.warn('Payload or topic is undefined/null') } + done(); }); node.serverConnection.register(node); @@ -463,7 +469,15 @@ module.exports = function(RED) { setStatusDisconnected(node); node.on("input", function(msg, send, done) { - node.serverConnection.ack(node.topic, msg.messageId, msg.transaction); + const topic = node.topic || msg.topic; + if (topic.length > 0) { + node.serverConnection.ack(topic, msg.messageId, msg.transaction); + } else if (!topic.length > 0) { + node.warn('No valid publish topic'); + + } else { + node.warn('Payload or topic is undefined/null') + } done(); }); From edd521f2e909a082c8b9d9849826bc16081252b8 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 8 Jun 2023 21:16:34 +0100 Subject: [PATCH 12/20] Bump for lPR to fix topic ack handling to close #1009 --- io/stomp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/stomp/package.json b/io/stomp/package.json index fd3c0eff..127399ea 100644 --- a/io/stomp/package.json +++ b/io/stomp/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-stomp", - "version" : "1.0.1", + "version" : "1.0.2", "description" : "A Node-RED node to publish and subscribe to/from a Stomp server", "dependencies" : { "stomp-client" : "^0.9.0" From 1216ee5296d08ec3734f8bfde04550424a3546b9 Mon Sep 17 00:00:00 2001 From: Olivier Verhaegen <56387556+OlivierVerhaegen@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:18:59 +0200 Subject: [PATCH 13/20] STOMP: improved logging (#1010) * Improvements to logging * Subscribe log clarification * Session id on connect fix --- io/stomp/18-stomp.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/io/stomp/18-stomp.js b/io/stomp/18-stomp.js index a28186aa..9b893a6e 100644 --- a/io/stomp/18-stomp.js +++ b/io/stomp/18-stomp.js @@ -184,14 +184,15 @@ module.exports = function(RED) { node.client = new StompClient(node.options); - node.client.on("connect", function() { + node.client.on("connect", function(sessionId) { node.closing = false; node.connecting = false; node.connected = true; - callback(); + node.sessionId = sessionId; - node.log("Connected to STOMP server", {sessionId: node.sessionId, url: `${node.options.address}:${node.options.port}`, protocolVersion: node.options.protocolVersion}); + node.log(`Connected to STOMP server, sessionId: ${node.sessionId}, url: ${node.options.address}:${node.options.port}, protocolVersion: ${node.options.protocolVersion}`); setStatusConnected(node, true); + callback(); }); node.client.on("reconnect", function(sessionId, numOfRetries) { @@ -199,10 +200,10 @@ module.exports = function(RED) { 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}); + 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); + callback(); }); node.client.on("reconnecting", function() { @@ -219,10 +220,7 @@ module.exports = function(RED) { setStatusError(node, true); }); - node.client.connect(function(sessionId) { - node.sessionId = sessionId; - }); - + node.client.connect(); } catch (err) { node.error(err); } @@ -258,14 +256,13 @@ module.exports = function(RED) { // 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}) + 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(() => { @@ -287,7 +284,7 @@ module.exports = function(RED) { * @param { Function } callback */ node.subscribe = function(queue, acknowledgment, callback) { - node.log(`Subscribing to: ${queue}`); + node.log(`Subscribe to: ${queue}`); if (node.connected && !node.closing) { if (!node.subscriptionIds[queue]) { @@ -323,7 +320,7 @@ module.exports = function(RED) { delete node.subscriptionIds[queue]; if (node.connected && !node.closing) { node.client.unsubscribe(queue, headers); - node.log(`Unsubscribed from ${queue}`, headers); + node.log(`Unsubscribed from ${queue}, headers: ${JSON.stringify(headers)}`); } } From 21b92d489464d96931e7372277574630be44af84 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 21 Jun 2023 20:20:15 +0100 Subject: [PATCH 14/20] Bump package for PR #1010 --- io/stomp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/stomp/package.json b/io/stomp/package.json index 127399ea..8edaf7e2 100644 --- a/io/stomp/package.json +++ b/io/stomp/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-stomp", - "version" : "1.0.2", + "version" : "1.0.3", "description" : "A Node-RED node to publish and subscribe to/from a Stomp server", "dependencies" : { "stomp-client" : "^0.9.0" From 51dc002a70a5b4e114403713e6a0ae2cdc2ca475 Mon Sep 17 00:00:00 2001 From: wooferguy Date: Sat, 24 Jun 2023 20:54:34 +1200 Subject: [PATCH 15/20] Pushover TTL (#1013) * TTL Functionality Pick up msg.ttl and pass it to REST API, checking it is a positive integer first. * Update help doc Reference ttl parameter in api docs --- social/pushover/57-pushover.html | 1 + social/pushover/57-pushover.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/social/pushover/57-pushover.html b/social/pushover/57-pushover.html index e3f9f130..8f641979 100644 --- a/social/pushover/57-pushover.html +++ b/social/pushover/57-pushover.html @@ -86,6 +86,7 @@

msg.sound: set the notification sound, see the available options

msg.retry: set retry interval for Emergency priority (2) messages, see details

msg.expire: set retry duration for Emergency priority (2) messages, see details

+

msg.ttl: set time to live duration on anything except Emergency priority (2) messages, see details

msg.callback: set the callback url for Emergency priority (2) messages, see details

msg.tags: set tags for Emergency priority (2) messages, see details

Uses Pushover. See this link for more details.

diff --git a/social/pushover/57-pushover.js b/social/pushover/57-pushover.js index 98603a69..3a77bbd7 100644 --- a/social/pushover/57-pushover.js +++ b/social/pushover/57-pushover.js @@ -41,6 +41,7 @@ module.exports = function(RED) { var attachment = msg.attachment || null; var retry = msg.retry || 30; var expire = msg.expire || 600; + var ttl = msg.ttl || null; var callback = msg.callback || null; var tags = msg.tags || null; if (isNaN(pri)) {pri=0;} @@ -62,6 +63,10 @@ module.exports = function(RED) { expire = 10800; node.warn("Expire time too high, using maximum setting of 10800s (3 hours) retry duration"); } + if (!Number.isInteger(ttl) || ttl<=0) { + ttl = null; + node.warn("No valid number for TTL found, not set"); + } if (typeof msg.payload === 'undefined') { msg.payload = "(undefined msg.payload)"; } if (typeof(msg.payload) === 'object') { msg.payload = JSON.stringify(msg.payload); @@ -74,6 +79,7 @@ module.exports = function(RED) { priority: pri, retry: retry, expire: expire, + ttl: ttl, html: html }; if (dev) { pushmsg.device = dev; } From a2b7e1a30c857f1d0d4b2f45dcf2f6adcad9f85a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sat, 24 Jun 2023 09:56:17 +0100 Subject: [PATCH 16/20] bump package for PR to add msg.ttl to release #1013 --- social/pushover/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social/pushover/package.json b/social/pushover/package.json index d913c923..1b327f38 100644 --- a/social/pushover/package.json +++ b/social/pushover/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-pushover", - "version": "0.1.1", + "version": "0.2.0", "description": "A Node-RED node to send alerts via Pushover", "dependencies": { "pushover-notifications": "^1.2.2" From 45b43ebb21cebcc55d7f02653373307a7637f033 Mon Sep 17 00:00:00 2001 From: Olivier Verhaegen <56387556+OlivierVerhaegen@users.noreply.github.com> Date: Tue, 11 Jul 2023 20:58:51 +0200 Subject: [PATCH 17/20] STOMP bugfix: usage of multiple stomp in nodes (#1015) * Bugfix: only execute node register callbacks after connection to the server has been made * Bugfix: only execute direct callback of register node register when connected to STOMP server * Doc: Add tip about reconnection to server --- io/stomp/18-stomp.html | 7 +++++-- io/stomp/18-stomp.js | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/io/stomp/18-stomp.html b/io/stomp/18-stomp.html index 4f2549a9..7d9b307c 100644 --- a/io/stomp/18-stomp.html +++ b/io/stomp/18-stomp.html @@ -16,7 +16,7 @@
-
+
Enabling the ACK (acknowledgment) will set the ack header to client 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 ack node.
@@ -73,7 +73,7 @@
-
The Destination field is optional. If not set uses the msg.topic +
The Destination field is optional. If not set uses the msg.topic property of the message.
@@ -147,6 +147,9 @@
+
+ Reconnection timings are calculated using exponential backoff. The first reconnection happens immediately, the second reconnection happens at +delay ms, the third at + 2*delay ms, etc. +
diff --git a/io/stomp/18-stomp.js b/io/stomp/18-stomp.js index 9b893a6e..97a2ec22 100644 --- a/io/stomp/18-stomp.js +++ b/io/stomp/18-stomp.js @@ -84,6 +84,8 @@ module.exports = function(RED) { node.sessionId = null; node.subscribtionIndex = 1; node.subscriptionIds = {}; + /** Array of callbacks to be called once the connection to the broker has been made */ + node.connectedCallbacks = []; /** @type { StompClient } */ node.client; node.setOptions = function(options, init) { @@ -134,10 +136,20 @@ module.exports = function(RED) { */ node.register = function(stompNode, callback = () => {}) { node.users[stompNode.id] = stompNode; + + if (!node.connected) { + node.connectedCallbacks.push(callback); + } + // Auto connect when first STOMP processing node is added if (Object.keys(node.users).length === 1) { - node.connect(callback); - } else { + node.connect(() => { + while (node.connectedCallbacks.length) { + node.connectedCallbacks.shift().call(); + } + }); + } else if (node.connected) { + // Execute callback directly as the connection to the STOMP server has already been made callback(); } } From a4d9b48ac73808bbbfddf313e27359215a352a7f Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 11 Jul 2023 20:00:17 +0100 Subject: [PATCH 18/20] bump for bugfix pr --- io/stomp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/stomp/package.json b/io/stomp/package.json index 8edaf7e2..5508ba7f 100644 --- a/io/stomp/package.json +++ b/io/stomp/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-stomp", - "version" : "1.0.3", + "version" : "1.0.4", "description" : "A Node-RED node to publish and subscribe to/from a Stomp server", "dependencies" : { "stomp-client" : "^0.9.0" From 8bf15d7a63a00f3a8542b1d8867f4ae649114ddb Mon Sep 17 00:00:00 2001 From: Li Huaqian <128661405+huaqianli@users.noreply.github.com> Date: Wed, 12 Jul 2023 03:12:25 +0800 Subject: [PATCH 19/20] Add user button label dynamically (#1016) Not all platforms have the user button supported, so dynamic to add the user button label. Signed-off-by: Li Hua Qian Signed-off-by: chao zeng Co-authored-by: chao zeng --- hardware/intel/mraa-gpio-din.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hardware/intel/mraa-gpio-din.html b/hardware/intel/mraa-gpio-din.html index df76b93e..83ef75fe 100644 --- a/hardware/intel/mraa-gpio-din.html +++ b/hardware/intel/mraa-gpio-din.html @@ -34,6 +34,9 @@ if (data === 7) { t = "Banana"; } if (data === 26) { t = "IOT2050"; } $('#type-tip').text(t); + if (data === 26) { + $('#node-input-pin').append($("").attr("value",20).text("USER button")); + } $('#node-input-pin').val(pinnow); }); $.getJSON('mraa-version/'+this.id,function(data) { @@ -68,7 +71,6 @@ -
From 512697eec4cdd6f988b29fdd324ea5b9dbb0b1ae Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 11 Jul 2023 20:17:55 +0100 Subject: [PATCH 20/20] Bump for pin label PR --- hardware/intel/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardware/intel/package.json b/hardware/intel/package.json index 6238514b..2c45353c 100644 --- a/hardware/intel/package.json +++ b/hardware/intel/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-intel-gpio", - "version" : "0.3.0", + "version" : "0.4.0", "description" : "A Node-RED node to talk to an Intel Galileo, Edison or Siemens IOT2050 board using mraa", "dependencies" : { },