node-red-contrib-knx-ultimate 2.1.46 → 2.1.47
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 +4 -0
- package/nodes/hue-config.js +121 -74
- package/nodes/knxUltimate-config.html +1 -15
- package/nodes/knxUltimate-config.js +420 -986
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 2.1.47</b> - September 2023<br/>
|
|
11
|
+
- HUE BRIDGE: fixed multiple HUE bridge handling.<br/>
|
|
12
|
+
<br/>
|
|
9
13
|
<p>
|
|
10
14
|
<b>Version 2.1.46</b> - September 2023<br/>
|
|
11
15
|
- HUE BRIDGE: In case of https problems (certificate expired, etc...), the node will try to connect to the HUE BRIDGE in insecure http mode.<br/>
|
package/nodes/hue-config.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
/* eslint-disable no-inner-declarations */
|
|
1
2
|
/* eslint-disable max-len */
|
|
2
|
-
const dptlib = require(
|
|
3
|
-
const HueClass = require(
|
|
4
|
-
const loggerEngine = require(
|
|
3
|
+
const dptlib = require("../KNXEngine/src/dptlib");
|
|
4
|
+
const HueClass = require("./utils/hueEngine").classHUE;
|
|
5
|
+
const loggerEngine = require("./utils/sysLogger");
|
|
5
6
|
// Helpers
|
|
6
7
|
const sortBy = (field) => (a, b) => {
|
|
7
|
-
if (a[field] > b[field]) {
|
|
8
|
+
if (a[field] > b[field]) {
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
return -1;
|
|
8
12
|
};
|
|
9
13
|
|
|
10
|
-
const onlyDptKeys = (kv) => kv[0].startsWith(
|
|
14
|
+
const onlyDptKeys = (kv) => kv[0].startsWith("DPT");
|
|
11
15
|
|
|
12
16
|
const extractBaseNo = (kv) => ({
|
|
13
17
|
subtypes: kv[1].subtypes,
|
|
14
|
-
base: parseInt(kv[1].id.replace(
|
|
18
|
+
base: parseInt(kv[1].id.replace("DPT", "")),
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
const convertSubtype = (baseType) => (kv) => {
|
|
@@ -25,20 +29,14 @@ const convertSubtype = (baseType) => (kv) => {
|
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
const toConcattedSubtypes = (acc, baseType) => {
|
|
28
|
-
const subtypes = Object.entries(baseType.subtypes)
|
|
29
|
-
.sort(sortBy(0))
|
|
30
|
-
.map(convertSubtype(baseType));
|
|
32
|
+
const subtypes = Object.entries(baseType.subtypes).sort(sortBy(0)).map(convertSubtype(baseType));
|
|
31
33
|
|
|
32
34
|
return acc.concat(subtypes);
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
module.exports = (RED) => {
|
|
36
|
-
RED.httpAdmin.get(
|
|
37
|
-
const dpts = Object.entries(dptlib)
|
|
38
|
-
.filter(onlyDptKeys)
|
|
39
|
-
.map(extractBaseNo)
|
|
40
|
-
.sort(sortBy('base'))
|
|
41
|
-
.reduce(toConcattedSubtypes, []);
|
|
38
|
+
RED.httpAdmin.get("/knxUltimateDpts", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
39
|
+
const dpts = Object.entries(dptlib).filter(onlyDptKeys).map(extractBaseNo).sort(sortBy("base")).reduce(toConcattedSubtypes, []);
|
|
42
40
|
|
|
43
41
|
res.json(dpts);
|
|
44
42
|
});
|
|
@@ -48,12 +46,14 @@ module.exports = (RED) => {
|
|
|
48
46
|
const node = this;
|
|
49
47
|
node.host = config.host;
|
|
50
48
|
node.nodeClients = []; // Stores the registered clients
|
|
51
|
-
node.loglevel = config.loglevel !== undefined ? config.loglevel :
|
|
49
|
+
node.loglevel = config.loglevel !== undefined ? config.loglevel : "error"; // 18/02/2020 Loglevel default error
|
|
52
50
|
node.sysLogger = null;
|
|
53
51
|
try {
|
|
54
52
|
node.sysLogger = loggerEngine.get({ loglevel: node.loglevel }); // New logger to adhere to the loglevel selected in the config-window
|
|
55
|
-
} catch (error) {
|
|
56
|
-
|
|
53
|
+
} catch (error) {
|
|
54
|
+
/* empty */
|
|
55
|
+
}
|
|
56
|
+
node.name = config.name === undefined || config.name === "" ? node.host : config.name;
|
|
57
57
|
|
|
58
58
|
// Init HUE Utility
|
|
59
59
|
node.hueManager = new HueClass(node.host, node.credentials.username, node.credentials.clientkey, config.bridgeid, node.sysLogger);
|
|
@@ -64,8 +64,10 @@ module.exports = (RED) => {
|
|
|
64
64
|
// Handle events
|
|
65
65
|
try {
|
|
66
66
|
node.hueManager.removeAllListeners();
|
|
67
|
-
} catch (error) {
|
|
68
|
-
|
|
67
|
+
} catch (error) {
|
|
68
|
+
/* empty */
|
|
69
|
+
}
|
|
70
|
+
node.hueManager.on("event", (_event) => {
|
|
69
71
|
node.nodeClients.forEach((_oClient) => {
|
|
70
72
|
const oClient = _oClient;
|
|
71
73
|
try {
|
|
@@ -76,18 +78,20 @@ module.exports = (RED) => {
|
|
|
76
78
|
});
|
|
77
79
|
});
|
|
78
80
|
// Connected
|
|
79
|
-
node.hueManager.on(
|
|
80
|
-
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(
|
|
81
|
+
node.hueManager.on("connected", () => {
|
|
82
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("node.hueManager connected event");
|
|
81
83
|
});
|
|
82
84
|
// Initialize the http wrapper, to use the provided key.
|
|
83
85
|
// This http wrapper is used to get the data from HUE brigde
|
|
84
86
|
try {
|
|
85
87
|
// Load all resources, to avoid too many call to the HUE bridge and speed up the showing of the device mnames, during typing in the config window
|
|
86
|
-
node.hueAllResources = await node.hueManager.hueApiV2.get(
|
|
87
|
-
node.hueAllRooms = await node.hueManager.hueApiV2.get(
|
|
88
|
-
node.hueAllDevices = await node.hueManager.hueApiV2.get(
|
|
88
|
+
node.hueAllResources = await node.hueManager.hueApiV2.get("/resource");
|
|
89
|
+
node.hueAllRooms = await node.hueManager.hueApiV2.get("/resource/room");
|
|
90
|
+
node.hueAllDevices = await node.hueManager.hueApiV2.get("/resource/device");
|
|
89
91
|
} catch (error) {
|
|
90
|
-
if (this.sysLogger !== undefined && this.sysLogger !== null)
|
|
92
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) {
|
|
93
|
+
this.sysLogger.error(`KNXUltimatehueEngine: classHUE: getting resources: ${error.message}`);
|
|
94
|
+
}
|
|
91
95
|
}
|
|
92
96
|
};
|
|
93
97
|
|
|
@@ -95,10 +99,11 @@ module.exports = (RED) => {
|
|
|
95
99
|
await node.ConnectToHueBridge();
|
|
96
100
|
})();
|
|
97
101
|
|
|
98
|
-
RED.httpAdmin.get(
|
|
102
|
+
RED.httpAdmin.get("/KNXUltimateGetResourcesHUE", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
99
103
|
try {
|
|
100
104
|
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
101
|
-
const
|
|
105
|
+
const serverNode = RED.nodes.getNode(req.query.nodeID); // Retrieve node.id of the config node.
|
|
106
|
+
const jRet = serverNode.getResources(req.query.rtype);
|
|
102
107
|
res.json(jRet);
|
|
103
108
|
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
104
109
|
} catch (error) {
|
|
@@ -117,14 +122,14 @@ module.exports = (RED) => {
|
|
|
117
122
|
|
|
118
123
|
// Returns capitalized string
|
|
119
124
|
function capStr(s) {
|
|
120
|
-
if (typeof s !==
|
|
125
|
+
if (typeof s !== "string") return "";
|
|
121
126
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
const retArray = [];
|
|
125
130
|
let allResources;
|
|
126
|
-
if (_rtype ===
|
|
127
|
-
allResources = node.hueAllResources.filter((a) => a.type ===
|
|
131
|
+
if (_rtype === "light" || _rtype === "grouped_light") {
|
|
132
|
+
allResources = node.hueAllResources.filter((a) => a.type === "light" || a.type === "grouped_light");
|
|
128
133
|
} else {
|
|
129
134
|
allResources = node.hueAllResources.filter((a) => a.type === _rtype);
|
|
130
135
|
}
|
|
@@ -132,69 +137,104 @@ module.exports = (RED) => {
|
|
|
132
137
|
const resource = allResources[index];
|
|
133
138
|
// Get the owner
|
|
134
139
|
try {
|
|
135
|
-
let resourceName =
|
|
136
|
-
let sRoom =
|
|
137
|
-
if (_rtype ===
|
|
140
|
+
let resourceName = "";
|
|
141
|
+
let sRoom = "";
|
|
142
|
+
if (_rtype === "light" || _rtype === "grouped_light") {
|
|
138
143
|
// It's a service, having a owner
|
|
139
144
|
const owners = node.hueAllResources.filter((a) => a.id === resource.owner.rid);
|
|
140
145
|
for (let index = 0; index < owners.length; index++) {
|
|
141
146
|
const owner = owners[index];
|
|
142
|
-
if (owner.type ===
|
|
143
|
-
resourceName +=
|
|
147
|
+
if (owner.type === "bridge_home") {
|
|
148
|
+
resourceName += "ALL GROUPS and ";
|
|
144
149
|
} else {
|
|
145
150
|
resourceName += `${owner.metadata.name} and `;
|
|
146
151
|
const room = node.hueAllRooms.find((child) => child.children.find((a) => a.rid === owner.id));
|
|
147
|
-
sRoom += room !== undefined ? `${room.metadata.name} + ` :
|
|
152
|
+
sRoom += room !== undefined ? `${room.metadata.name} + ` : " + ";
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
|
-
sRoom = sRoom.slice(0, -
|
|
151
|
-
resourceName = resourceName.slice(0, -
|
|
152
|
-
resourceName += sRoom !==
|
|
153
|
-
retArray.push({
|
|
155
|
+
sRoom = sRoom.slice(0, -" + ".length);
|
|
156
|
+
resourceName = resourceName.slice(0, -" and ".length);
|
|
157
|
+
resourceName += sRoom !== "" ? ` - Room: ${sRoom}` : "";
|
|
158
|
+
retArray.push({
|
|
159
|
+
name: `${capStr(resource.type)}: ${resourceName}`,
|
|
160
|
+
id: resource.id,
|
|
161
|
+
deviceObject: resource,
|
|
162
|
+
});
|
|
154
163
|
}
|
|
155
|
-
if (_rtype ===
|
|
156
|
-
resourceName = resource.metadata.name ||
|
|
164
|
+
if (_rtype === "scene") {
|
|
165
|
+
resourceName = resource.metadata.name || "**Name Not Found**";
|
|
157
166
|
// Get the linked zone
|
|
158
167
|
const zone = node.hueAllResources.find((res) => res.id === resource.group.rid);
|
|
159
168
|
resourceName += ` - ${capStr(resource.group.rtype)}: ${zone.metadata.name}`;
|
|
160
|
-
retArray.push({
|
|
169
|
+
retArray.push({
|
|
170
|
+
name: `${capStr(_rtype)}: ${resourceName}`,
|
|
171
|
+
id: resource.id,
|
|
172
|
+
});
|
|
161
173
|
}
|
|
162
|
-
if (_rtype ===
|
|
163
|
-
const linkedDevName =
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
if (_rtype === "button") {
|
|
175
|
+
const linkedDevName =
|
|
176
|
+
node.hueAllResources.find((dev) => dev.type === "device" && dev.services.find((serv) => serv.rid === resource.id)).metadata.name || "";
|
|
177
|
+
const controlID = resource.metadata !== undefined ? resource.metadata.control_id || "" : "";
|
|
178
|
+
retArray.push({
|
|
179
|
+
name: `${capStr(_rtype)}: ${linkedDevName}, button ${controlID}`,
|
|
180
|
+
id: resource.id,
|
|
181
|
+
});
|
|
166
182
|
}
|
|
167
|
-
if (_rtype ===
|
|
168
|
-
const linkedDevName =
|
|
169
|
-
|
|
183
|
+
if (_rtype === "motion") {
|
|
184
|
+
const linkedDevName =
|
|
185
|
+
node.hueAllResources.find((dev) => dev.type === "device" && dev.services.find((serv) => serv.rid === resource.id)).metadata.name || "";
|
|
186
|
+
retArray.push({
|
|
187
|
+
name: `${capStr(_rtype)}: ${linkedDevName}`,
|
|
188
|
+
id: resource.id,
|
|
189
|
+
});
|
|
170
190
|
}
|
|
171
|
-
if (_rtype ===
|
|
172
|
-
const linkedDevName =
|
|
173
|
-
|
|
191
|
+
if (_rtype === "relative_rotary") {
|
|
192
|
+
const linkedDevName =
|
|
193
|
+
node.hueAllResources.find((dev) => dev.type === "device" && dev.services.find((serv) => serv.rid === resource.id)).metadata.name || "";
|
|
194
|
+
retArray.push({
|
|
195
|
+
name: `Rotary: ${linkedDevName}`,
|
|
196
|
+
id: resource.id,
|
|
197
|
+
});
|
|
174
198
|
}
|
|
175
|
-
if (_rtype ===
|
|
199
|
+
if (_rtype === "light_level") {
|
|
176
200
|
const Room = node.hueAllRooms.find((room) => room.children.find((child) => child.rid === resource.owner.rid));
|
|
177
|
-
const linkedDevName =
|
|
178
|
-
|
|
201
|
+
const linkedDevName =
|
|
202
|
+
node.hueAllResources.find((dev) => dev.type === "device" && dev.services.find((serv) => serv.rid === resource.id)).metadata.name || "";
|
|
203
|
+
retArray.push({
|
|
204
|
+
name: `Light Level: ${linkedDevName}${Room !== undefined ? `, room ${Room.metadata.name}` : ""}`,
|
|
205
|
+
id: resource.id,
|
|
206
|
+
});
|
|
179
207
|
}
|
|
180
|
-
if (_rtype ===
|
|
208
|
+
if (_rtype === "temperature") {
|
|
181
209
|
const Room = node.hueAllRooms.find((room) => room.children.find((child) => child.rid === resource.owner.rid));
|
|
182
|
-
const linkedDevName =
|
|
183
|
-
|
|
210
|
+
const linkedDevName =
|
|
211
|
+
node.hueAllResources.find((dev) => dev.type === "device" && dev.services.find((serv) => serv.rid === resource.id)).metadata.name || "";
|
|
212
|
+
retArray.push({
|
|
213
|
+
name: `Temperature: ${linkedDevName}${Room !== undefined ? `, room ${Room.metadata.name}` : ""}`,
|
|
214
|
+
id: resource.id,
|
|
215
|
+
});
|
|
184
216
|
}
|
|
185
|
-
if (_rtype ===
|
|
217
|
+
if (_rtype === "device_power") {
|
|
186
218
|
const Room = node.hueAllRooms.find((room) => room.children.find((child) => child.rid === resource.owner.rid));
|
|
187
|
-
const linkedDevName =
|
|
188
|
-
|
|
219
|
+
const linkedDevName =
|
|
220
|
+
node.hueAllResources.find((dev) => dev.type === "device" && dev.services.find((serv) => serv.rid === resource.id)).metadata.name || "";
|
|
221
|
+
retArray.push({
|
|
222
|
+
name: `Battery: ${linkedDevName}${Room !== undefined ? `, room ${Room.metadata.name}` : ""}`,
|
|
223
|
+
id: resource.id,
|
|
224
|
+
});
|
|
189
225
|
}
|
|
190
226
|
} catch (error) {
|
|
191
|
-
retArray.push({
|
|
227
|
+
retArray.push({
|
|
228
|
+
name: `${_rtype}: ERROR ${error.message}`,
|
|
229
|
+
id: resource.id,
|
|
230
|
+
});
|
|
192
231
|
}
|
|
193
232
|
}
|
|
194
233
|
return { devices: retArray };
|
|
195
234
|
} catch (error) {
|
|
196
|
-
if (node.sysLogger !== undefined && node.sysLogger !== null)
|
|
197
|
-
|
|
235
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null)
|
|
236
|
+
node.sysLogger.error(`KNXUltimateHue: hueEngine: classHUE: getDevices: error ${error.message}`);
|
|
237
|
+
return { devices: error.message };
|
|
198
238
|
}
|
|
199
239
|
};
|
|
200
240
|
|
|
@@ -202,7 +242,11 @@ module.exports = (RED) => {
|
|
|
202
242
|
// Check if node already exists
|
|
203
243
|
if (node.nodeClients.filter((x) => x.id === _Node.id).length === 0) {
|
|
204
244
|
// Add _Node to the clients array
|
|
205
|
-
_Node.setNodeStatusHue({
|
|
245
|
+
_Node.setNodeStatusHue({
|
|
246
|
+
fill: "grey",
|
|
247
|
+
shape: "ring",
|
|
248
|
+
text: "Hue initialized.",
|
|
249
|
+
});
|
|
206
250
|
node.nodeClients.push(_Node);
|
|
207
251
|
}
|
|
208
252
|
};
|
|
@@ -211,12 +255,13 @@ module.exports = (RED) => {
|
|
|
211
255
|
// Remove the client node from the clients array
|
|
212
256
|
try {
|
|
213
257
|
node.nodeClients = node.nodeClients.filter((x) => x.id !== _Node.id);
|
|
214
|
-
} catch (error) {
|
|
258
|
+
} catch (error) {}
|
|
215
259
|
};
|
|
216
260
|
|
|
217
|
-
node.on(
|
|
261
|
+
node.on("close", (done) => {
|
|
218
262
|
try {
|
|
219
|
-
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger = null;
|
|
263
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger = null;
|
|
264
|
+
loggerEngine.destroy();
|
|
220
265
|
node.nodeClients = [];
|
|
221
266
|
node.hueManager.removeAllListeners();
|
|
222
267
|
(async () => {
|
|
@@ -224,7 +269,9 @@ module.exports = (RED) => {
|
|
|
224
269
|
await node.hueManager.close();
|
|
225
270
|
node.hueManager = null;
|
|
226
271
|
delete node.hueManager;
|
|
227
|
-
} catch (error) {
|
|
272
|
+
} catch (error) {
|
|
273
|
+
/* empty */
|
|
274
|
+
}
|
|
228
275
|
done();
|
|
229
276
|
})();
|
|
230
277
|
} catch (error) {
|
|
@@ -234,10 +281,10 @@ module.exports = (RED) => {
|
|
|
234
281
|
}
|
|
235
282
|
|
|
236
283
|
// RED.nodes.registerType("hue-config", hue-config);
|
|
237
|
-
RED.nodes.registerType(
|
|
284
|
+
RED.nodes.registerType("hue-config", hueConfig, {
|
|
238
285
|
credentials: {
|
|
239
|
-
username: { type:
|
|
240
|
-
clientkey: { type:
|
|
286
|
+
username: { type: "password" },
|
|
287
|
+
clientkey: { type: "password" },
|
|
241
288
|
},
|
|
242
289
|
});
|
|
243
290
|
};
|
|
@@ -111,21 +111,7 @@
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
// 14/08/2021 Elimino il file delle persistenze di questo nodo
|
|
114
|
-
$.getJSON("deletePersistGAFile?nodeID=" + node.id, (data) => {
|
|
115
|
-
// var myNotification = RED.notify("Persistence group addresses file for this gateway has been deleted. That's normal.",
|
|
116
|
-
// {
|
|
117
|
-
// modal: false,
|
|
118
|
-
// fixed: false,
|
|
119
|
-
// type: 'info',
|
|
120
|
-
// buttons: [
|
|
121
|
-
// {
|
|
122
|
-
// text: "OK",
|
|
123
|
-
// click: function (e) {
|
|
124
|
-
// myNotification.close();
|
|
125
|
-
// }
|
|
126
|
-
// }]
|
|
127
|
-
// })
|
|
128
|
-
});
|
|
114
|
+
$.getJSON("deletePersistGAFile?nodeID=" + node.id, (data) => {});
|
|
129
115
|
|
|
130
116
|
// 06/07/2023 Tabs
|
|
131
117
|
// *****************************
|