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 +4 -0
- package/http2Testing/myhttp2.js +103 -0
- package/http2Testing/ratelimiter.js +24 -0
- package/http2Testing/ratelimitertestbed.js +7 -0
- package/http2Testing/testbed.js +36 -0
- package/nodes/utils/http.js +11 -5
- package/nodes/utils/hueEngine.js +32 -12
- package/package.json +2 -1
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,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
|
+
|
package/nodes/utils/http.js
CHANGED
|
@@ -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
|
}
|
package/nodes/utils/hueEngine.js
CHANGED
|
@@ -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
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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.
|
|
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",
|