node-red-contrib-knx-ultimate 3.3.29 → 3.3.31

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.
package/CHANGELOG.md CHANGED
@@ -6,7 +6,11 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
- **Version 3.3.29** - April 2025<br/>
9
+ **Version 3.3.31** - April 2025<br/>
10
+ - **BREAKING CHANGE** **!!!!!!!**: node must be >=18.0.0 (needed before was >=16.0.0).**!!!!!!!** **BREAKING CHANGE**<br/>
11
+ - HUE: getting rid of the Eventsource package, due to sleepy connections not recognized.<br/>
12
+
13
+ **Version 3.3.30** - April 2025<br/>
10
14
  - **BREAKING CHANGE** **!!!!!!!**: node must be >=18.0.0 (needed before was >=16.0.0).**!!!!!!!** **BREAKING CHANGE**<br/>
11
15
  - HUE: fixed SSE Hue Bridge silent disconnection issue.<br/>
12
16
 
@@ -5,7 +5,7 @@
5
5
  /* eslint-disable no-inner-declarations */
6
6
  /* eslint-disable max-len */
7
7
  const cloneDeep = require("lodash/cloneDeep");
8
- const HueClass = require("./utils/hueEngine").classHUE;
8
+ //const classHUE = require("./utils/hueEngine").classHUE;
9
9
  const hueColorConverter = require("./utils/colorManipulators/hueColorConverter");
10
10
 
11
11
 
@@ -47,64 +47,67 @@ module.exports = (RED) => {
47
47
  try {
48
48
  if (node.hueManager !== undefined) await node.hueManager.close();
49
49
  } catch (error) { /* empty */ }
50
- try {
51
- // Init HUE Utility
52
- node.hueManager = new HueClass(node.host, node.credentials.username, node.credentials.clientkey, config.bridgeid, node.sysLogger);
53
- } catch (error) {
54
- node.sysLogger?.error(`Errore hue-config: node.initHUEConnection: ${error.message}`);
55
- throw (error)
56
- }
57
- node.hueManager.on("event", (_event) => {
58
- node.nodeClients.forEach((_oClient) => {
59
- const oClient = _oClient;
60
- try {
61
- if (oClient.handleSendHUE !== undefined) oClient.handleSendHUE(_event);
62
- } catch (error) {
63
- node.sysLogger?.error(`Errore node.hueManager.on(event): ${error.message}`);
50
+
51
+ (async () => {
52
+ try {
53
+ const { classHUE } = await import('./utils/hueEngine.mjs');
54
+ node.hueManager = new classHUE(node.host, node.credentials.username, node.credentials.clientkey, config.bridgeid, node.sysLogger);
55
+ } catch (error) {
56
+ node.sysLogger?.error(`Errore hue-config: node.initHUEConnection: ${error.message}`);
57
+ throw (error)
58
+ }
59
+ node.hueManager.on("event", (_event) => {
60
+ node.nodeClients.forEach((_oClient) => {
61
+ const oClient = _oClient;
62
+ try {
63
+ if (oClient.handleSendHUE !== undefined) oClient.handleSendHUE(_event);
64
+ } catch (error) {
65
+ node.sysLogger?.error(`Errore node.hueManager.on(event): ${error.message}`);
66
+ }
67
+ });
68
+ });
69
+ // Connected
70
+ node.hueManager.on("connected", () => {
71
+ if (node.linkStatus === "disconnected") {
72
+ // Start the timer to do initial read.
73
+ if (node.timerDoInitialRead !== null) clearTimeout(node.timerDoInitialRead);
74
+ node.timerDoInitialRead = setTimeout(() => {
75
+ (async () => {
76
+ try {
77
+ node.sysLogger?.info(`HTTP getting resource from HUE bridge : ${node.name}`);
78
+ await node.loadResourcesFromHUEBridge();
79
+ node.sysLogger?.info(`Total HUE resources count : ${node.hueAllResources.length}`);
80
+ } catch (error) {
81
+ node.nodeClients.forEach((_oClient) => {
82
+ setTimeout(() => {
83
+ _oClient.setNodeStatusHue({
84
+ fill: "red",
85
+ shape: "ring",
86
+ text: "HUE",
87
+ payload: error.message,
88
+ });
89
+ }, 200);
90
+ });
91
+ }
92
+ })();
93
+ }, 10000); // 17/02/2020 Do initial read of all nodes requesting initial read
64
94
  }
65
95
  });
66
- });
67
- // Connected
68
- node.hueManager.on("connected", () => {
69
- if (node.linkStatus === "disconnected") {
70
- // Start the timer to do initial read.
71
- if (node.timerDoInitialRead !== null) clearTimeout(node.timerDoInitialRead);
72
- node.timerDoInitialRead = setTimeout(() => {
73
- (async () => {
74
- try {
75
- node.sysLogger?.info(`HTTP getting resource from HUE bridge : ${node.name}`);
76
- await node.loadResourcesFromHUEBridge();
77
- node.sysLogger?.info(`Total HUE resources count : ${node.hueAllResources.length}`);
78
- } catch (error) {
79
- node.nodeClients.forEach((_oClient) => {
80
- setTimeout(() => {
81
- _oClient.setNodeStatusHue({
82
- fill: "red",
83
- shape: "ring",
84
- text: "HUE",
85
- payload: error.message,
86
- });
87
- }, 200);
88
- });
89
- }
90
- })();
91
- }, 10000); // 17/02/2020 Do initial read of all nodes requesting initial read
92
- }
93
- });
94
96
 
95
- node.hueManager.on("disconnected", () => {
96
- node.nodeClients.forEach((_oClient) => {
97
- _oClient.setNodeStatusHue({
98
- fill: "red",
99
- shape: "ring",
100
- text: "HUE Disconnected",
101
- payload: "",
97
+ node.hueManager.on("disconnected", () => {
98
+ node.nodeClients.forEach((_oClient) => {
99
+ _oClient.setNodeStatusHue({
100
+ fill: "red",
101
+ shape: "ring",
102
+ text: "HUE Disconnected",
103
+ payload: "",
104
+ });
102
105
  });
103
106
  });
104
- });
105
- try {
106
- await node.hueManager.Connect();
107
- } catch (error) { }
107
+ try {
108
+ await node.hueManager.Connect();
109
+ } catch (error) { }
110
+ })();
108
111
 
109
112
  };
110
113
 
@@ -122,12 +125,16 @@ module.exports = (RED) => {
122
125
  }
123
126
  await node.startWatchdogTimer();
124
127
  })();
125
- }, 20000);
128
+ }, 60000);
126
129
  };
127
130
 
128
- (async () => {
129
- await node.startWatchdogTimer();
130
- })();
131
+ setTimeout(() => {
132
+ (async () => {
133
+ await node.initHUEConnection();
134
+ node.startWatchdogTimer();
135
+ })();
136
+ }, 5000);
137
+
131
138
 
132
139
  // Functions called from the nodes ----------------------------------------------------------------
133
140
  // Query the HUE Bridge to return the resources
@@ -0,0 +1,100 @@
1
+ import { EventEmitter } from 'events';
2
+ import { fetch, Agent } from 'undici';
3
+ import { setTimeout as delay } from 'timers/promises';
4
+
5
+ class HueEventStream extends EventEmitter {
6
+ constructor(url, { headers = {}, reconnectInterval = 5000 } = {}) {
7
+ super();
8
+ this.url = url;
9
+ this.headers = headers;
10
+ this.reconnectInterval = reconnectInterval;
11
+ this.abortController = null;
12
+ this.agent = new Agent({
13
+ connect: {
14
+ rejectUnauthorized: false
15
+ }
16
+ });
17
+ this.connected = false;
18
+ this._start();
19
+ }
20
+
21
+ async _start() {
22
+ while (true) {
23
+ try {
24
+ this.abortController = new AbortController();
25
+ const res = await fetch(this.url, {
26
+ headers: this.headers,
27
+ dispatcher: this.agent,
28
+ signal: this.abortController.signal
29
+ });
30
+
31
+ if (res.status !== 200) {
32
+ this.emit('error', new Error(`Unexpected status: ${res.status}`));
33
+ await delay(this.reconnectInterval);
34
+ continue;
35
+ }
36
+
37
+ this.emit('open');
38
+ this.connected = true;
39
+
40
+ let buffer = '';
41
+ for await (const chunk of res.body) {
42
+ buffer += chunk.toString();
43
+
44
+ const lines = buffer.split(/\r?\n\r?\n/);
45
+ buffer = lines.pop(); // l'ultima parte potrebbe essere incompleta
46
+
47
+ for (const block of lines) {
48
+ const event = this._parseEvent(block);
49
+ if (event) this.emit('message', event);
50
+ }
51
+ }
52
+
53
+ this.emit('close');
54
+ this.connected = false;
55
+ await delay(this.reconnectInterval);
56
+ } catch (err) {
57
+ if (err.name !== 'AbortError') {
58
+ this.emit('error', err);
59
+ await delay(this.reconnectInterval);
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ _parseEvent(block) {
66
+ const lines = block.split(/\r?\n/);
67
+ let data = '';
68
+ let event = 'message';
69
+
70
+ for (const line of lines) {
71
+ if (line.startsWith('data:')) {
72
+ data += line.slice(5).trim();
73
+ } else if (line.startsWith('event:')) {
74
+ event = line.slice(6).trim();
75
+ }
76
+ }
77
+
78
+ try {
79
+ return {
80
+ type: event,
81
+ data: data ? JSON.parse(data) : null
82
+ };
83
+ } catch (e) {
84
+ return {
85
+ type: event,
86
+ data: data || null
87
+ };
88
+ }
89
+ }
90
+
91
+ close() {
92
+ if (this.abortController) {
93
+ this.abortController.abort();
94
+ this.abortController = null;
95
+ }
96
+ this.connected = false;
97
+ }
98
+ }
99
+
100
+ export { HueEventStream };
@@ -0,0 +1,213 @@
1
+ import { EventEmitter } from 'events';
2
+ import { fetch, Agent } from 'undici';
3
+ import { setTimeout as pleaseWait } from 'timers/promises';
4
+ import * as http from './http.js';
5
+
6
+ class classHUE extends EventEmitter {
7
+ constructor(_hueBridgeIP, _username, _clientkey, _bridgeid, _sysLogger) {
8
+ super();
9
+ this.HUEBridgeConnectionStatus = "disconnected";
10
+ this.exitAllQueues = false;
11
+ this.hueBridgeIP = _hueBridgeIP;
12
+ this.username = _username;
13
+ this.clientkey = _clientkey;
14
+ this.bridgeid = _bridgeid;
15
+ this.commandQueue = [];
16
+ this.sysLogger = _sysLogger;
17
+ this.timerCheckConnected = null;
18
+ this.restartSSECounter = 0;
19
+ this.handleQueue();
20
+ this.eventStreamAbort = null;
21
+ }
22
+
23
+ Connect = async () => {
24
+ if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
25
+ if (this.eventStreamAbort) this.eventStreamAbort.abort();
26
+
27
+ this.hueApiV2 = http.use({
28
+ key: this.username,
29
+ prefix: `https://${this.hueBridgeIP}/clip/v2`
30
+ });
31
+
32
+ const agent = new Agent({
33
+ connect: { rejectUnauthorized: false }
34
+ });
35
+
36
+ const headers = {
37
+ 'hue-application-key': this.username,
38
+ 'Accept': 'text/event-stream',
39
+ 'Cache-control': 'no-cache'
40
+ };
41
+
42
+ this.eventStreamAbort = new AbortController();
43
+
44
+ const url = `https://${this.hueBridgeIP}/eventstream/clip/v2`;
45
+
46
+ try {
47
+ const res = await fetch(url, {
48
+ headers,
49
+ dispatcher: agent,
50
+ signal: this.eventStreamAbort.signal
51
+ });
52
+
53
+ if (res.status !== 200) {
54
+ this.emit('error', new Error(`Status ${res.status}`));
55
+ return;
56
+ }
57
+
58
+ this.emit("connected");
59
+ this.HUEBridgeConnectionStatus = "connected";
60
+ this.sysLogger?.info(`classHUE: connected to SSE`);
61
+
62
+ this.timerCheckConnected = setInterval(() => {
63
+ (async () => {
64
+ try {
65
+ this.restartSSECounter += 1;
66
+ if (this.restartSSECounter >= 2) {
67
+ this.sysLogger?.debug(`Restarted SSE Client, per sicurezza, altrimenti potrebbe addormentarsi`);
68
+ this.restartSSECounter = 0;
69
+ this.eventStreamAbort.abort();
70
+ await this.Connect();
71
+ return;
72
+ }
73
+ const jReturn = await this.hueApiV2.get('/resource/bridge');
74
+ if (!Array.isArray(jReturn) || jReturn.length < 1) throw new Error("Bridge not found");
75
+ this.HUEBridgeConnectionStatus = "connected";
76
+ } catch (error) {
77
+ this.sysLogger?.error(`Ping ERROR: ${error.message}`);
78
+ if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
79
+ this.commandQueue = [];
80
+ try { await this.close(); } catch (error) { }
81
+ this.restartSSECounter = 0;
82
+ this.emit("disconnected");
83
+ }
84
+ })();
85
+ }, 12000);
86
+
87
+ let buffer = '';
88
+ const textDecoder = new TextDecoder();
89
+ for await (const chunk of res.body) {
90
+ buffer += textDecoder.decode(chunk, { stream: true });
91
+ let parts = buffer.split(/\r?\n\r?\n/);
92
+ if (parts.length > 1) {
93
+ buffer = parts.pop();
94
+ } else {
95
+ buffer = parts[0];
96
+ parts = [];
97
+ }
98
+
99
+ for (const block of parts) {
100
+ const parsed = this._parseEvent(block);
101
+ if (parsed?.data && Array.isArray(parsed.data)) {
102
+ parsed.data.forEach(ev => {
103
+ for (let index = 0; index < ev.data.length; index++) {
104
+ const element = ev.data[index];
105
+ this.emit("event", element)
106
+ }
107
+ });
108
+ }
109
+ }
110
+ }
111
+ } catch (err) {
112
+ if (err.name !== 'AbortError' && this.sysLogger) {
113
+ this.sysLogger.error(`EventStream error: ${err.message}`)
114
+ this.commandQueue = [];
115
+ try { await this.close(); } catch (error) { }
116
+ this.restartSSECounter = 0;
117
+ this.emit("disconnected");
118
+ };
119
+ }
120
+ };
121
+
122
+ _parseEvent = (block) => {
123
+ const lines = block.split(/\r?\n/);
124
+ let data = '';
125
+ let event = 'message';
126
+
127
+ for (const line of lines) {
128
+ if (line.startsWith('data:')) {
129
+ data += line.slice(5).trim();
130
+ } else if (line.startsWith('event:')) {
131
+ event = line.slice(6).trim();
132
+ }
133
+ }
134
+
135
+ try {
136
+ return {
137
+ type: event,
138
+ data: data ? JSON.parse(data) : null
139
+ };
140
+ } catch (e) {
141
+ return {
142
+ type: event,
143
+ data: data || null
144
+ };
145
+ }
146
+ };
147
+
148
+ processQueueItem = async () => {
149
+ try {
150
+ const jRet = this.commandQueue.pop();
151
+ switch (jRet._operation) {
152
+ case "setLight":
153
+ await this.hueApiV2.put(`/resource/light/${jRet._lightID}`, jRet._state);
154
+ break;
155
+ case "setGroupedLight":
156
+ await this.hueApiV2.put(`/resource/grouped_light/${jRet._lightID}`, jRet._state);
157
+ break;
158
+ case "setScene":
159
+ await this.hueApiV2.put(`/resource/scene/${jRet._lightID}`, jRet._state);
160
+ break;
161
+ case "stopScene":
162
+ const allResources = await this.hueApiV2.get("/resource");
163
+ const jScene = allResources.find((res) => res.id === jRet._lightID);
164
+ const linkedLight = allResources.find((res) => res.id === jScene.group.rid).children || [];
165
+ linkedLight.forEach((light) => {
166
+ this.writeHueQueueAdd(light.rid, jRet._state, "setLight");
167
+ });
168
+ break;
169
+ default:
170
+ break;
171
+ }
172
+ } catch (error) {
173
+ this.sysLogger?.error(`processQueueItem: ${error.message}`);
174
+ }
175
+ };
176
+
177
+ handleQueue = async () => {
178
+ do {
179
+ if (this.commandQueue && this.commandQueue.length > 0) {
180
+ try {
181
+ await this.processQueueItem();
182
+ } catch (error) { }
183
+ }
184
+ await pleaseWait(150);
185
+ } while (!this.exitAllQueues);
186
+ };
187
+
188
+ writeHueQueueAdd = async (_lightID, _state, _operation) => {
189
+ this.commandQueue.unshift({ _lightID, _state, _operation });
190
+ };
191
+
192
+ deleteHueQueue = async (_lightID) => {
193
+ this.commandQueue = this.commandQueue.filter((el) => el._lightID !== _lightID);
194
+ };
195
+
196
+ close = async () =>
197
+ new Promise((resolve, reject) => {
198
+ if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
199
+ try {
200
+ this.exitAllQueues = true;
201
+ this.restartSSECounter = 0;
202
+ try {
203
+ if (this.eventStreamAbort) this.eventStreamAbort.abort();
204
+ } catch (error) { }
205
+ this.HUEBridgeConnectionStatus = "disconnected";
206
+ resolve(true);
207
+ } catch (error) {
208
+ reject(error);
209
+ }
210
+ });
211
+ }
212
+
213
+ export { classHUE };
package/package.json CHANGED
@@ -3,22 +3,22 @@
3
3
  "engines": {
4
4
  "node": ">=18.0.0"
5
5
  },
6
- "version": "3.3.29",
6
+ "version": "3.3.31",
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",
10
10
  "crypto-js": "4.2.0",
11
11
  "dns-sync": "0.2.1",
12
- "eventsource": "2.0.2",
13
12
  "js-yaml": "4.1.0",
14
13
  "knxultimate": "4.1.1",
15
14
  "lodash": "4.17.21",
16
- "node-color-log": "12.0.1",
17
15
  "mkdirp": "3.0.1",
16
+ "node-color-log": "12.0.1",
18
17
  "node-hue-api": "5.0.0-beta.16",
19
18
  "path": "0.12.7",
20
19
  "ping": "0.4.4",
21
20
  "simple-get": "4.0.1",
21
+ "undici": "^7.8.0",
22
22
  "xml2js": "0.6.0"
23
23
  },
24
24
  "node-red": {
@@ -1,256 +0,0 @@
1
- /* eslint-disable max-len */
2
- const { EventEmitter } = require("events");
3
- const EventSource = require("eventsource");
4
- const http = require("./http");
5
- const { setTimeout: pleaseWait } = require('timers/promises');
6
- //const { forEach } = require("lodash");
7
- // Configura il rate limiter
8
- //const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 150 }); // HUE telegram interval
9
-
10
- class classHUE extends EventEmitter {
11
-
12
- constructor(_hueBridgeIP, _username, _clientkey, _bridgeid, _sysLogger) {
13
- super();
14
- this.HUEBridgeConnectionStatus = "disconnected";
15
- this.exitAllQueues = false;
16
- this.hueBridgeIP = _hueBridgeIP;
17
- this.username = _username;
18
- this.clientkey = _clientkey;
19
- this.bridgeid = _bridgeid;
20
- this.commandQueue = [];
21
- // eslint-disable-next-line max-len
22
- this.sysLogger = _sysLogger;
23
- this.timerCheckConnected = null;
24
- this.restartSSECounter = 0; // To auto reset the SSE Connection
25
- this.handleQueue();
26
- }
27
-
28
- Connect = () => {
29
- if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
30
-
31
- const options = {
32
- headers: {
33
- "hue-application-key": this.username,
34
- pragma: "no-cache",
35
- "cache-control": "no-cache,no-store, must-revalidate",
36
- },
37
- https: {
38
- rejectUnauthorized: false,
39
- },
40
- };
41
-
42
- // Init the http to use the username and bridge ip
43
- this.hueApiV2 = http.use({
44
- key: this.username,
45
- prefix: `https://${this.hueBridgeIP}/clip/v2`
46
- });
47
-
48
- try {
49
- if (this.es !== null && this.es !== undefined) {
50
- (async () => {
51
- await this.close();
52
- this.exitAllQueues = false;
53
- })();
54
- }
55
- } catch (error) {
56
- /* empty */
57
- }
58
-
59
- try {
60
- this.es = new EventSource(`https://${this.hueBridgeIP}/eventstream/clip/v2`, options);
61
- } catch (error) {
62
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`hueEngine: ew EventSource: ${error.message}`);
63
- }
64
-
65
-
66
- this.es.onmessage = (event) => {
67
- try {
68
- if (event && event.type === "message" && event.data) {
69
- const data = JSON.parse(event.data);
70
- data.forEach((element) => {
71
- if (element.type === "update") {
72
- element.data.forEach((ev) => {
73
- this.emit("event", ev);
74
- });
75
- }
76
- });
77
- }
78
- } catch (error) {
79
- if (this.sysLogger !== undefined && this.sysLogger !== null)
80
- this.sysLogger.error(`KNXUltimatehueEngine: classHUE: this.es.onmessage: ${error.message}`);
81
- }
82
- };
83
-
84
- this.es.onopen = () => {
85
- // if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: SSE-Connected')
86
- this.emit("connected");
87
- this.HUEBridgeConnectionStatus = "connected";
88
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.info(`KNXUltimatehueEngine: classHUE: this.es.onopen: connected`);
89
- // Check wether the hue bridge is connected or not
90
- if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
91
- this.timerCheckConnected = setInterval(() => {
92
- (async () => {
93
- try {
94
- this.restartSSECounter += 1;
95
- if (this.restartSSECounter >= 6) {
96
- // Restart SSE client, due to silent disconnection affecting the SSE server
97
- this.restartSSECounter = 0;
98
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug(`KNXUltimatehueEngine: classHUE:this.timerCheckConnected = setInterval: reconnection to the eventsource`);
99
- this.es.close();
100
- this.es = new EventSource(`https://${this.hueBridgeIP}/eventstream/clip/v2`, options);
101
- }
102
-
103
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug(`KNXUltimatehueEngine: classHUE: Pinging...`);
104
- const jReturn = await this.hueApiV2.get('/resource/bridge');
105
- if (!Array.isArray(jReturn) || jReturn.length < 1) throw new Error("jReturn: not an array or array empty")
106
- this.HUEBridgeConnectionStatus = "connected";
107
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug(`KNXUltimatehueEngine: classHUE: Ping OK`);
108
-
109
- } catch (error) {
110
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: Ping ERROR: ${error.message}`);
111
- if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
112
- this.commandQueue = [];
113
- try {
114
- await this.close();
115
- } catch (error) { }
116
- this.restartSSECounter = 0;
117
- this.emit("disconnected");
118
- }
119
- })();
120
- }, 120000);
121
- };
122
-
123
- this.es.onerror = (error) => {
124
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: this.es.onopen: ${error.message}`);
125
- // (async () => {
126
- // await this.close();
127
- // if (this.HUEBridgeConnectionStatus === 'connected') this.emit('disconnected');
128
- // })();
129
-
130
- // 29/08/2023 NON riattivare, perchè alla disconnessione, va in loop e consuma tutto il pool di risorse.
131
- // try {
132
- // this.es.close();
133
- // this.es = null;
134
- // if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: request.on(error): ${error.message}`);
135
- // } catch (err) { /* empty */ }
136
- // this.Connect();
137
- // // this.emit('error', error)
138
- // };
139
- };
140
- };
141
-
142
- // Process single item in the queue
143
- processQueueItem = async () => {
144
- try {
145
- const jRet = this.commandQueue.pop();
146
- // jRet is ({ _lightID, _state, _operation });;
147
- switch (jRet._operation) {
148
- case "setLight":
149
- // It can be a light or a grouped light
150
- try {
151
- const ok = await this.hueApiV2.put(`/resource/light/${jRet._lightID}`, jRet._state);
152
- } catch (error) {
153
- if (this.sysLogger !== undefined && this.sysLogger !== null) {
154
- this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: setLight light: ${error.message}.`);
155
- }
156
- }
157
- break;
158
- case "setGroupedLight":
159
- try {
160
- await this.hueApiV2.put(`/resource/grouped_light/${jRet._lightID}`, jRet._state);
161
- } catch (error) {
162
- if (this.sysLogger !== undefined && this.sysLogger !== null)
163
- this.sysLogger.info(`KNXUltimatehueEngine: classHUE: processQueueItem: setLight grouped_light: ${error.message}`);
164
- }
165
- break;
166
- case "setScene":
167
- try {
168
- const sceneID = jRet._lightID;
169
- await this.hueApiV2.put(`/resource/scene/${sceneID}`, jRet._state);
170
- } catch (error) {
171
- if (this.sysLogger !== undefined && this.sysLogger !== null)
172
- this.sysLogger.info(`KNXUltimatehueEngine: classHUE: processQueueItem: setScene: ${error.message}`);
173
- }
174
- break;
175
- case "stopScene":
176
- try {
177
- const allResources = await this.hueApiV2.get("/resource");
178
- const sceneID = jRet._lightID;
179
- const jScene = allResources.find((res) => res.id === sceneID) || "";
180
- const linkedLight = allResources.find((res) => res.id === jScene.group.rid).children || "";
181
- linkedLight.forEach((light) => {
182
- this.writeHueQueueAdd(light.rid, jRet._state, "setLight");
183
- });
184
- } catch (error) {
185
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: stopScene: ${error.message}`);
186
- }
187
- break;
188
- default:
189
- break;
190
- }
191
- } catch (error) {
192
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: ${error.trace}`);
193
- }
194
- };
195
-
196
- // // Handle the send queue
197
- // // ######################################
198
- handleQueue = async () => {
199
- // Verifica se è possibile eseguire una nuova richiesta
200
- do {
201
- if (this.commandQueue !== undefined && this.commandQueue.length > 0) {
202
- //if (remainingRequests >= 0) {
203
- // OK, i can send
204
- //console.log("\x1b[32m Messaggio. remainingRequests=" + remainingRequests + "\x1b[0m " + new Date().toTimeString(), this.commandQueue.length, "remainingRequests " + remainingRequests);
205
- try {
206
- await this.processQueueItem();
207
- } catch (error) {
208
- }
209
- //} else {
210
- // Limit reached, skip this round.
211
- //console.log("\x1b[41m HO DETTO SPETA. remainingRequests=" + remainingRequests + "\x1b[0m " + new Date().toTimeString(), this.commandQueue.length, "remainingRequests " + remainingRequests);
212
- //}
213
- }
214
- await pleaseWait(150);
215
- } while (!this.exitAllQueues);
216
- //console.log("\x1b[42m End processing commandQueue \x1b[0m " + new Date().toTimeString(), this.commandQueue.length);
217
- };
218
-
219
-
220
- writeHueQueueAdd = async (_lightID, _state, _operation) => {
221
- // Add the new item
222
- this.commandQueue.unshift({ _lightID, _state, _operation });
223
- };
224
-
225
- /**
226
- * Clears all items fo _lightID from the HUE sending queue. Useful to clear unwanted dimming commands
227
- * @param {string} _lightID HUE Light ID
228
- * @returns {}
229
- */
230
- deleteHueQueue = async (_lightID) => {
231
- // Add the new item
232
- this.commandQueue = this.commandQueue.filter((el) => el._lightID !== _lightID);
233
- };
234
- // ######################################
235
-
236
- close = async () =>
237
- new Promise((resolve, reject) => {
238
- if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
239
- try {
240
- this.exitAllQueues = true;
241
- this.restartSSECounter = 0;
242
- setTimeout(() => {
243
- try {
244
- if (this.es !== null && this.es !== undefined) this.es.close();
245
- if (this.es !== null && this.es !== undefined) this.es.removeEventListener();
246
- } catch (error) { }
247
- this.es = null;
248
- this.HUEBridgeConnectionStatus = "disconnected";
249
- resolve(true);
250
- }, 2000);
251
- } catch (error) {
252
- reject(error);
253
- }
254
- });
255
- }
256
- module.exports.classHUE = classHUE;