node-red-contrib-knx-ultimate 3.1.9 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of node-red-contrib-knx-ultimate might be problematic. Click here for more details.

package/CHANGELOG.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 3.2.0** - September 2024<br/>
10
+ - Major Version.<br/>
11
+ - HUE engine: implemented a new "limiter" package for better calculate the maximum numbers of telegrams accepted by HUE Bridge.<br/>
12
+
9
13
  **Version 3.1.9** - September 2024<br/>
10
14
  - HUE Bridge: you can now register to a bridge with your own credentials.<br/>
11
15
  - HUE Bridge: you can reveal the encrypted bridge's credentials (there is a button for that).<br/>
@@ -0,0 +1,103 @@
1
+
2
+
3
+ const http2 = require('node:http2');
4
+ //import http2, { ClientHttp2Session, IncomingHttpHeaders, IncomingHttpStatusHeader } from "http2";
5
+
6
+ class Http2Client {
7
+ /**
8
+ * Parameters.
9
+ *
10
+ * @param config.key the credentualskey.
11
+ * @param config.url the bridge's URL
12
+ * @param config.path the resource url
13
+ * @param options the standard http2 options
14
+ */
15
+ constructor(config, options) {
16
+ options.rejectUnauthorized = false;
17
+ options.headers = {
18
+ 'hue-application-key': config.key,
19
+ };
20
+ this.bridgeKey = config.key;
21
+ this.http2Connection = http2.connect(config.url, options);
22
+ this.http2Connection.on('error', (err) => {
23
+ console.error(err);
24
+ this.http2Connection.close();
25
+ });
26
+ }
27
+
28
+ async get(path, headers) {
29
+ await this.executeRequest({ ":method": "GET", ':path': path, ...headers });
30
+ }
31
+
32
+
33
+ async put(path, headers, body) {
34
+ await this.executeRequest({ ":method": "PUT", ':path': path, ...headers }, body);
35
+ }
36
+
37
+ async post(path, headers, body) {
38
+ await this.executeRequest({ ":method": "POST", ':path': path, ...headers }, body);
39
+ }
40
+
41
+ async delete(path, headers) {
42
+ await this.executeRequest({ ":method": "DELETE", ':path': path, ...headers });
43
+ }
44
+
45
+ executeRequest(headers, body) {
46
+ headers['headers'] = {
47
+ 'hue-application-key': this.bridgeKey
48
+ };
49
+ return new Promise((resolve, reject) => {
50
+ const stream = this.http2Connection.request(headers);
51
+ if (body !== undefined) {
52
+ stream.write(JSON.stringify(body), 'utf8');
53
+ }
54
+
55
+ stream.setEncoding('utf8');
56
+ let response = {
57
+ headers: {},
58
+ data: ""
59
+ }
60
+ stream.on('response', (responseHeaders) => {
61
+ response.headers = responseHeaders;
62
+ });
63
+
64
+ stream.on('data', (chunk) => {
65
+ response.data += chunk;
66
+ });
67
+
68
+ stream.on('end', () => {
69
+ stream.close();
70
+ resolve(response.data);
71
+ });
72
+
73
+ stream.on('error', (e) => {
74
+ reject(e);
75
+ });
76
+
77
+ stream.end();
78
+ });
79
+ }
80
+
81
+
82
+ createEventSource(path, headers, onData, onClose) {
83
+ const stream = this.http2Connection.request({ ":method": "GET", ':path': path, "Accept": "text/event-stream", ...headers });
84
+ stream.setEncoding('utf8');
85
+
86
+ // Each data event will contain a single event from the Hue bridge.
87
+ stream.on('data', (data) => {
88
+ onData(data);
89
+ });
90
+ stream.on('end', () => {
91
+ stream.close();
92
+ onClose();
93
+ });
94
+ stream.on('error', (error) => {
95
+ console.error(error);
96
+ stream.close();
97
+ onClose(error);
98
+ });
99
+ stream.end();
100
+ }
101
+
102
+ }
103
+ module.exports = Http2Client
@@ -0,0 +1,24 @@
1
+ const { RateLimiter } = require('limiter');
2
+ // Configura il rate limiter
3
+ const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 150 });
4
+
5
+ console.log("Parto");
6
+ async function Vai(params) {
7
+ do {
8
+
9
+ // Verifica se è possibile eseguire una nuova richiesta
10
+ const remainingRequests = await limiter.removeTokens(1);
11
+ if (remainingRequests >= 0) {
12
+ // Richiesta consentita
13
+ console.log("Messaggio. remainingRequests=" + remainingRequests + " " + new Date().toTimeString());
14
+ } else {
15
+ // Limite superato
16
+ console.log("HO DETTO SPETA. remainingRequests=" + remainingRequests + " " + new Date().toTimeString());
17
+ }
18
+
19
+ } while (true);
20
+ }
21
+ Vai();
22
+ console.log("Fine");
23
+
24
+
@@ -0,0 +1,7 @@
1
+ const RateLimiter = require("./ratelimiter");
2
+
3
+ async function ba(params) {
4
+ await RateLimiter.sendMessage()
5
+ }
6
+
7
+ ba;
@@ -0,0 +1,36 @@
1
+ const Http2Client = require('./myhttp2');
2
+ const bridgeIp = '192.168.1.36';
3
+ const bridgeUsername = '';
4
+ const Clientkey = '';
5
+ const ca = ``; // Certificato https://developers.meethue.com/develop/application-design-guidance/using-https/
6
+
7
+ const http2Client = new Http2Client(
8
+ {
9
+ url: `https://${bridgeIp}`,
10
+ key: bridgeUsername
11
+ },
12
+ {
13
+
14
+ // ca: ca,
15
+ // checkServerIdentity: function (hostname, cert) {
16
+ // if (cert.subject.CN === this.bridge.getId().toLowerCase()) {
17
+ // console.log("Successful server identity check!");
18
+ // return undefined;
19
+ // } else {
20
+ // return new Error("Server identity check failed. CN does not match bridgeId.");
21
+ // }
22
+ // },
23
+ });
24
+
25
+ console.log(http2Client.get(`https://${bridgeIp}/clip/v2/resource/device`));
26
+
27
+ // async (params) => {
28
+ // await http2Client.createEventSource(
29
+ // "/eventstream/clip/v2",
30
+ // { "hue-application-key": bridgeUsername },
31
+ // (data) => { console.log(data); },
32
+ // (error) => { console.log(`Event source closed! ${error}`); }
33
+ // );
34
+ // }
35
+
36
+
@@ -1,10 +1,13 @@
1
- /**
2
- * Promise wrapper around simple get.
3
- * Modified code based on: https://github.com/rodney42/node-hue/blob/main/lib/http.js
4
- * Thank you rodney42
5
- */
1
+
6
2
  const simpleget = require('simple-get');
7
3
 
4
+ /**
5
+ * Parameters.
6
+ *
7
+ * @param config.key the credentualskey.
8
+ * @param config.prefix the bridge's URL
9
+ * @param config.url the resource url
10
+ */
8
11
  module.exports.use = (config) => {
9
12
  let http = {};
10
13
 
@@ -24,6 +27,7 @@ module.exports.use = (config) => {
24
27
  simpleget.concat(opt, (err, res, data) => {
25
28
  try {
26
29
  if (err) {
30
+ RED.log.error(`utils.https: config.http.call: simpleget.concat ${err.message} : ${err.stack || ""} `);
27
31
  reject(err);
28
32
  } else {
29
33
  // log.trace('http data ' + data);
@@ -31,6 +35,7 @@ module.exports.use = (config) => {
31
35
  try {
32
36
  let result = JSON.parse(data);
33
37
  if (result.errors && result.errors.length > 0) {
38
+ //console.log("\x1b[41m OrroreUno \x1b[0m result.errors ", result.errors, " " + new Date().toTimeString(), this.commandQueue.length, "remainingRequests " + remainingRequests);
34
39
  reject(new Error("The response for " + opt.url + " returned errors " + JSON.stringify(result.errors)));
35
40
  }
36
41
  if (!result.data) {
@@ -41,6 +46,7 @@ module.exports.use = (config) => {
41
46
  RED.log.error(`utils.https: config.http.call: let result = JSON.parse(data); =: ${error.message} : ${error.stack || ""} `);
42
47
  }
43
48
  } else {
49
+ RED.log.error(`utils.https: config.http.call: simpleget.concat: Error response: status code: ${res.statusCode || undefined}`);
44
50
  reject(new Error("Error response for " + opt.url + " with status " + res.statusCode + " " + res.statusMessage));
45
51
  }
46
52
  }
@@ -2,7 +2,11 @@
2
2
  const { EventEmitter } = require("events");
3
3
  const EventSource = require("eventsource");
4
4
  const http = require("./http");
5
- const pleaseWait = t => new Promise((resolve, reject) => setTimeout(resolve, t))
5
+ // const pleaseWait = t => new Promise((resolve, reject) => setTimeout(resolve, t))
6
+ const { RateLimiter } = require('limiter');
7
+ //const { forEach } = require("lodash");
8
+ // Configura il rate limiter
9
+ const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 150 }); // HUE telegram interval
6
10
 
7
11
  class classHUE extends EventEmitter {
8
12
 
@@ -108,12 +112,10 @@ class classHUE extends EventEmitter {
108
112
  }, 10 * (60 * 1000)); // 10 minutes
109
113
  };
110
114
 
111
- // Handle the send queue
112
- // ######################################
113
- handleQueue = async () => {
114
- if (this.commandQueue.length > 0) {
115
- // const jRet = { ...this.commandQueue.shift() } //Clone the object by value
116
- const jRet = this.commandQueue.shift();
115
+ // Process single item in the queue
116
+ processQueueItem = async () => {
117
+ try {
118
+ const jRet = this.commandQueue.pop();
117
119
  // jRet is ({ _lightID, _state, _operation });;
118
120
  switch (jRet._operation) {
119
121
  case "setLight":
@@ -153,8 +155,7 @@ class classHUE extends EventEmitter {
153
155
  this.writeHueQueueAdd(light.rid, jRet._state, "setLight");
154
156
  });
155
157
  } catch (error) {
156
- if (this.sysLogger !== undefined && this.sysLogger !== null)
157
- this.sysLogger.error(`KNXUltimatehueEngine: classHUE: handleQueue: stopScene: ${error.message}`);
158
+ if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: handleQueue: stopScene: ${error.message}`);
158
159
  }
159
160
  break;
160
161
  case "Ping":
@@ -170,10 +171,29 @@ class classHUE extends EventEmitter {
170
171
  default:
171
172
  break;
172
173
  }
174
+ } catch (error) {
175
+ if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: ${error.trace}`);
173
176
  }
174
- // The Hue bridge allows about 10 telegram per second, so i need to make a queue manager
175
- await pleaseWait(150); // Waits
176
- if (this.closePushEventStream === false) this.handleQueue();
177
+ };
178
+
179
+ // // Handle the send queue
180
+ // // ######################################
181
+ handleQueue = async () => {
182
+ // Verifica se è possibile eseguire una nuova richiesta
183
+ do {
184
+ const remainingRequests = await limiter.removeTokens(1);
185
+ if (this.commandQueue.length > 0) {
186
+ //if (remainingRequests >= 0) {
187
+ // OK, i can send
188
+ //console.log("\x1b[32m Messaggio. remainingRequests=" + remainingRequests + "\x1b[0m " + new Date().toTimeString(), this.commandQueue.length, "remainingRequests " + remainingRequests);
189
+ await this.processQueueItem();
190
+ //} else {
191
+ // Limit reached, skip this round.
192
+ //console.log("\x1b[41m HO DETTO SPETA. remainingRequests=" + remainingRequests + "\x1b[0m " + new Date().toTimeString(), this.commandQueue.length, "remainingRequests " + remainingRequests);
193
+ //}
194
+ }
195
+ } while (this.closePushEventStream === false);
196
+ //console.log("\x1b[42m End processing commandQueue \x1b[0m " + new Date().toTimeString(), this.commandQueue.length);
177
197
  };
178
198
 
179
199
  writeHueQueueAdd = async (_lightID, _state, _operation) => {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "3.1.9",
6
+ "version": "3.2.0",
7
7
  "description": "Control your KNX intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.",
8
8
  "dependencies": {
9
9
  "binary-parser": "2.2.1",
@@ -12,6 +12,7 @@
12
12
  "eventsource": "2.0.2",
13
13
  "js-yaml": "4.1.0",
14
14
  "knxultimate": ">=2.3.5",
15
+ "limiter": "^2.1.0",
15
16
  "lodash": "4.17.21",
16
17
  "log-driver": "1.2.7",
17
18
  "mkdirp": "3.0.1",