node-red-contrib-knx-ultimate 2.3.4 → 2.4.1
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/.github/ISSUE_TEMPLATE/bug_report.md +7 -7
- package/CHANGELOG.md +29 -0
- package/KNXEngine/CHANGELOG.md +5 -1
- package/KNXEngine/package.json +1 -1
- package/KNXEngine/src/dptlib/dpt275.js +60 -0
- package/KNXEngine/src/dptlib/index.js +79 -65
- package/nodes/commonFunctions.js +419 -0
- package/nodes/hue-config.html +56 -40
- package/nodes/hue-config.js +0 -108
- package/nodes/knxUltimate-config.html +1 -1
- package/nodes/knxUltimate-config.js +13 -232
- package/nodes/knxUltimate.html +2 -2
- package/nodes/knxUltimateHueBattery.html +2 -2
- package/nodes/knxUltimateHueButton.html +4 -4
- package/nodes/knxUltimateHueLight.html +989 -947
- package/nodes/knxUltimateHueLight.js +50 -26
- package/nodes/knxUltimateHueLightSensor.html +2 -2
- package/nodes/knxUltimateHueMotion.html +2 -2
- package/nodes/knxUltimateHueScene.html +4 -4
- package/nodes/knxUltimateHueTapDial.html +2 -2
- package/nodes/knxUltimateHueTemperatureSensor.html +2 -2
- package/nodes/knxUltimateLoadControl.html +1 -1
- package/nodes/knxUltimateSceneController.html +6 -6
- package/nodes/knxUltimateSceneController.js +1 -9
- package/package.json +2 -2
- package/nodes/utils/commonFunctions.js +0 -21
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
// Utility function
|
|
2
|
+
// until node-red 3.1.0, there is a bug creating a plugin, so for backward compatibility, i must use a JS as a node.
|
|
3
|
+
const oOS = require("os");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const yaml = require('js-yaml');
|
|
7
|
+
const loggerEngine = require("./utils/sysLogger.js");
|
|
8
|
+
const dptlib = require("../KNXEngine/src/dptlib");
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// DATAPONT MANIPULATION HELPERS
|
|
13
|
+
// ####################
|
|
14
|
+
const sortBy = (field) => (a, b) => {
|
|
15
|
+
if (a[field] > b[field]) {
|
|
16
|
+
return 1;
|
|
17
|
+
} else {
|
|
18
|
+
return -1;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const onlyDptKeys = (kv) => {
|
|
23
|
+
return kv[0].startsWith("DPT");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const extractBaseNo = (kv) => {
|
|
27
|
+
return {
|
|
28
|
+
subtypes: kv[1].subtypes,
|
|
29
|
+
base: parseInt(kv[1].id.replace("DPT", "")),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const convertSubtype = (baseType) => (kv) => {
|
|
34
|
+
const value = `${baseType.base}.${kv[0]}`;
|
|
35
|
+
// let sRet = value + " " + kv[1].name + (kv[1].unit === undefined ? "" : " (" + kv[1].unit + ")");
|
|
36
|
+
const sRet = value + " " + kv[1].name;
|
|
37
|
+
return {
|
|
38
|
+
value,
|
|
39
|
+
text: sRet,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const toConcattedSubtypes = (acc, baseType) => {
|
|
44
|
+
const subtypes = Object.entries(baseType.subtypes).sort(sortBy(0)).map(convertSubtype(baseType));
|
|
45
|
+
|
|
46
|
+
return acc.concat(subtypes);
|
|
47
|
+
};
|
|
48
|
+
// ####################
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
module.exports = function (RED) {
|
|
52
|
+
const node = this;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
node.sysLogger = loggerEngine.get({ loglevel: node.loglevel }); // 08/04/2021 new logger to adhere to the loglevel selected in the config-window
|
|
56
|
+
} catch (error) { }
|
|
57
|
+
|
|
58
|
+
// 11/03/2020 Delete scene saved file, from html
|
|
59
|
+
RED.httpAdmin.get('/knxultimateCheckHueConnected', (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
62
|
+
if (serverId.hueAllResources === null || serverId.hueAllResources === undefined) {
|
|
63
|
+
res.json({ ready: false });
|
|
64
|
+
} else {
|
|
65
|
+
res.json({ ready: true });
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
res.json({ ready: false });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
// 11/03/2020 Delete scene saved file, from html
|
|
74
|
+
RED.httpAdmin.get('/knxultimatescenecontrollerdelete', RED.auth.needsPermission('knxUltimateSceneController.read'), (req, res) => {
|
|
75
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
76
|
+
// Delete the file
|
|
77
|
+
try {
|
|
78
|
+
const newPath = `${serverId.userDir}/scenecontroller/SceneController_${req.query.FileName}`;
|
|
79
|
+
fs.unlinkSync(newPath);
|
|
80
|
+
} catch (error) { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn(`e ${error}`); }
|
|
81
|
+
res.json({ status: 220 });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Endpoint for connecting to HUE Bridge
|
|
85
|
+
RED.httpAdmin.get("/KNXUltimateRegisterToHueBridge", (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
|
|
88
|
+
(async () => {
|
|
89
|
+
try {
|
|
90
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
91
|
+
|
|
92
|
+
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
93
|
+
// If using this code outside of the examples directory, you will want to use the line below and remove the
|
|
94
|
+
// const discovery = require('node-hue-api').discovery
|
|
95
|
+
const hueApi = require("node-hue-api").api;
|
|
96
|
+
const appName = "KNXUltimate";
|
|
97
|
+
const deviceName = "Node-Red";
|
|
98
|
+
|
|
99
|
+
// async function discoverBridge() {
|
|
100
|
+
// const discoveryResults = await discovery.nupnpSearch()
|
|
101
|
+
|
|
102
|
+
// if (discoveryResults.length === 0) {
|
|
103
|
+
// if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error('Failed to resolve any Hue Bridges')
|
|
104
|
+
// return null
|
|
105
|
+
// } else {
|
|
106
|
+
// // Ignoring that you could have more than one Hue Bridge on a network as this is unlikely in 99.9% of users situations
|
|
107
|
+
// return discoveryResults[0].ipaddress
|
|
108
|
+
// }
|
|
109
|
+
// }
|
|
110
|
+
async function discoverAndCreateUser() {
|
|
111
|
+
// const ipAddress = await discoverBridge()
|
|
112
|
+
const ipAddress = req.query.IP;
|
|
113
|
+
// Create an unauthenticated instance of the Hue API so that we can create a new user
|
|
114
|
+
try {
|
|
115
|
+
const unauthenticatedApi = await hueApi.createLocal(ipAddress).connect();
|
|
116
|
+
let createdUser;
|
|
117
|
+
createdUser = await unauthenticatedApi.users.createUser(appName, deviceName);
|
|
118
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("*******************************************************************************\n");
|
|
119
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) {
|
|
120
|
+
node.sysLogger.info(
|
|
121
|
+
"User has been created on the Hue Bridge. The following username can be used to\n"
|
|
122
|
+
+ "authenticate with the Bridge and provide full local access to the Hue Bridge.\n"
|
|
123
|
+
+ "YOU SHOULD TREAT THIS LIKE A PASSWORD\n",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Hue Bridge User: ${createdUser.username}`);
|
|
127
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Hue Bridge User Client Key: ${createdUser.clientkey}`);
|
|
128
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("*******************************************************************************\n");
|
|
129
|
+
|
|
130
|
+
// Create a new API instance that is authenticated with the new user we created
|
|
131
|
+
const authenticatedApi = await hueApi.createLocal(ipAddress).connect(createdUser.username);
|
|
132
|
+
// Do something with the authenticated user/api
|
|
133
|
+
const bridgeConfig = await authenticatedApi.configuration.getConfiguration();
|
|
134
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Connected to Hue Bridge: ${bridgeConfig.name} :: ${bridgeConfig.ipaddress}`);
|
|
135
|
+
return { bridge: bridgeConfig, user: createdUser };
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`The Link button on the bridge was not pressed. ${err.message}`);
|
|
138
|
+
throw err;
|
|
139
|
+
// return {
|
|
140
|
+
// error:
|
|
141
|
+
// "The Link button on the bridge was not pressed or an error has occurred. " +
|
|
142
|
+
// err.message,
|
|
143
|
+
// };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function discoverAndCreateUserInsecure() {
|
|
147
|
+
// const ipAddress = await discoverBridge()
|
|
148
|
+
const ipAddress = req.query.IP;
|
|
149
|
+
|
|
150
|
+
// Create an unauthenticated instance of the Hue API so that we can create a new user
|
|
151
|
+
try {
|
|
152
|
+
const unauthenticatedApi = await hueApi.createInsecureLocal(ipAddress).connect();
|
|
153
|
+
let createdUser;
|
|
154
|
+
createdUser = await unauthenticatedApi.users.createUser(appName, deviceName);
|
|
155
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("*******************************************************************************\n");
|
|
156
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) {
|
|
157
|
+
node.sysLogger.info(
|
|
158
|
+
"User has been created on the Hue Bridge. The following username can be used to\n"
|
|
159
|
+
+ "authenticate with the Bridge and provide full local access to the Hue Bridge.\n"
|
|
160
|
+
+ "YOU SHOULD TREAT THIS LIKE A PASSWORD\n",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Hue Bridge User: ${createdUser.username}`);
|
|
164
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Hue Bridge User Client Key: ${createdUser.clientkey}`);
|
|
165
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("*******************************************************************************\n");
|
|
166
|
+
|
|
167
|
+
// Create a new API instance that is authenticated with the new user we created
|
|
168
|
+
const authenticatedApi = await hueApi.createInsecureLocal(ipAddress).connect(createdUser.username);
|
|
169
|
+
// Do something with the authenticated user/api
|
|
170
|
+
const bridgeConfig = await authenticatedApi.configuration.getConfiguration();
|
|
171
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Connected to Hue Bridge: ${bridgeConfig.name} :: ${bridgeConfig.ipaddress}`);
|
|
172
|
+
return { bridge: bridgeConfig, user: createdUser };
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`The Link button on the bridge was not pressed. ` + err.stack);
|
|
175
|
+
return {
|
|
176
|
+
error: `The Link button on the bridge was not pressed or an error has occurred.`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Invoke the discovery and create user code
|
|
182
|
+
try {
|
|
183
|
+
const jRet = await discoverAndCreateUser();
|
|
184
|
+
res.json(jRet);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
RED.log.error(`Errore KNXUltimateRegisterToHueBridge non gestito Secure ${error.message}. Try with insecure http connection...`);
|
|
187
|
+
// Try with insecureClient (avoid problems with expired https certificates)
|
|
188
|
+
try {
|
|
189
|
+
const jRet = await discoverAndCreateUserInsecure();
|
|
190
|
+
res.json(jRet);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
RED.log.error(`Errore KNXUltimateRegisterToHueBridge non gestito Insecure ${error.message}. I give up.`);
|
|
193
|
+
res.json({ error: error.message });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
RED.log.error(`Errore KNXUltimateRegisterToHueBridge non gestito ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
})();
|
|
200
|
+
} catch (err) {
|
|
201
|
+
RED.log.error(`Errore KNXUltimateRegisterToHueBridge bsonto ${err.message}`);
|
|
202
|
+
res.json({ error: err.message });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Endpoint for reading csv/esf by the other nodes
|
|
207
|
+
RED.httpAdmin.get("/knxUltimatecsv", RED.auth.needsPermission("knxUltimate-config.read"), (req, res) => {
|
|
208
|
+
if (typeof req.query.nodeID !== "undefined" && req.query.nodeID !== null && req.query.nodeID !== "") {
|
|
209
|
+
const _node = RED.nodes.getNode(req.query.nodeID); // Retrieve node.id of the config node.
|
|
210
|
+
if (_node !== null) res.json(RED.nodes.getNode(_node.id).csv);
|
|
211
|
+
} else {
|
|
212
|
+
// Get the first knxultimate-config having a valid csv
|
|
213
|
+
try {
|
|
214
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Requested csv maybe from visu-ultimate?");
|
|
215
|
+
RED.nodes.eachNode((_node) => {
|
|
216
|
+
if (_node.hasOwnProperty("csv") && _node.type === "knxUltimate-config" && _node.csv !== "") {
|
|
217
|
+
res.json(RED.nodes.getNode(_node.id).csv);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
} catch (error) { }
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 14/08/2019 Endpoint for retrieving the ethernet interfaces
|
|
225
|
+
RED.httpAdmin.get("/knxUltimateETHInterfaces", RED.auth.needsPermission("knxUltimate-config.read"), (req, res) => {
|
|
226
|
+
|
|
227
|
+
const oiFaces = oOS.networkInterfaces();
|
|
228
|
+
const jListInterfaces = [];
|
|
229
|
+
try {
|
|
230
|
+
Object.keys(oiFaces).forEach((ifname) => {
|
|
231
|
+
// Interface with single IP
|
|
232
|
+
if (Object.keys(oiFaces[ifname]).length === 1) {
|
|
233
|
+
if (Object.keys(oiFaces[ifname])[0].internal === false) {
|
|
234
|
+
jListInterfaces.push({
|
|
235
|
+
name: ifname,
|
|
236
|
+
address: Object.keys(oiFaces[ifname])[0].address,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
let sAddresses = "";
|
|
241
|
+
oiFaces[ifname].forEach((iface) => {
|
|
242
|
+
if (iface.internal === false) sAddresses += `+${iface.address}`;
|
|
243
|
+
});
|
|
244
|
+
if (sAddresses !== "") jListInterfaces.push({ name: ifname, address: sAddresses });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
} catch (error) { }
|
|
248
|
+
res.json(jListInterfaces);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// 12/08/2021 Endpoint for deleting the GA persistent file for the current gateway
|
|
252
|
+
RED.httpAdmin.get("/deletePersistGAFile", RED.auth.needsPermission("knxUltimate-config.read"), (req, res) => {
|
|
253
|
+
if (typeof req.query.serverId !== "undefined" && req.query.serverId !== null && req.query.serverId !== "") {
|
|
254
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
255
|
+
const sFile = path.join(serverId.userDir, "knxpersistvalues", `knxpersist${req.query.serverId}.json`);
|
|
256
|
+
try {
|
|
257
|
+
fs.unlinkSync(sFile);
|
|
258
|
+
} catch (error) { }
|
|
259
|
+
res.json({ error: "No error" });
|
|
260
|
+
} else {
|
|
261
|
+
res.json({ error: "No serverId specified" });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
RED.httpAdmin.get("/knxUltimateGetHueColor", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
266
|
+
try {
|
|
267
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
268
|
+
// find wether the light is a light or is grouped_light
|
|
269
|
+
let hexColor;
|
|
270
|
+
const _oDevice = serverId.hueAllResources.filter((a) => a.id === req.query.id)[0];
|
|
271
|
+
if (_oDevice.type === "light") {
|
|
272
|
+
hexColor = serverId.getColorFromHueLight(req.query.id);
|
|
273
|
+
} else {
|
|
274
|
+
// grouped_light, get the first light in the group
|
|
275
|
+
const oLight = serverId.getFirstLightInGroup(_oDevice.id);
|
|
276
|
+
hexColor = serverId.getColorFromHueLight(oLight.id);
|
|
277
|
+
}
|
|
278
|
+
res.json(hexColor !== undefined ? hexColor : "Select the device first!");
|
|
279
|
+
} catch (error) {
|
|
280
|
+
res.json("Select the device first!");
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
RED.httpAdmin.get("/knxUltimateGetKelvinColor", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
284
|
+
try {
|
|
285
|
+
// find wether the light is a light or is grouped_light
|
|
286
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
287
|
+
let kelvinValue;
|
|
288
|
+
const _oDevice = serverId.hueAllResources.filter((a) => a.id === req.query.id)[0];
|
|
289
|
+
if (_oDevice.type === "light") {
|
|
290
|
+
kelvinValue = serverId.getKelvinFromHueLight(req.query.id);
|
|
291
|
+
} else {
|
|
292
|
+
// grouped_light, get the first light in the group
|
|
293
|
+
const oLight = serverId.getFirstLightInGroup(_oDevice.id);
|
|
294
|
+
kelvinValue = serverId.getKelvinFromHueLight(oLight.id);
|
|
295
|
+
}
|
|
296
|
+
res.json(kelvinValue !== undefined ? kelvinValue : "Select the device first!");
|
|
297
|
+
} catch (error) {
|
|
298
|
+
res.json("Select the device first!");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
RED.httpAdmin.get("/knxUltimateGetLightObject", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
303
|
+
try {
|
|
304
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
305
|
+
if (serverId.hueAllResources === null || serverId.hueAllResources === undefined) {
|
|
306
|
+
throw (new Error("Resource not yet loaded"));
|
|
307
|
+
}
|
|
308
|
+
const _lightId = req.query.id;
|
|
309
|
+
const oLight = serverId.hueAllResources.filter((a) => a.id === _lightId)[0];
|
|
310
|
+
// Infer some useful info, so the HTML part can avoid to query the server
|
|
311
|
+
// Kelvin
|
|
312
|
+
try {
|
|
313
|
+
if (oLight.color_temperature !== undefined && oLight.color_temperature.mirek !== undefined) {
|
|
314
|
+
oLight.calculatedKelvin = hueColorConverter.ColorConverter.mirekToKelvin(oLight.color_temperature.mirek);
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
oLight.calculatedKelvin = undefined;
|
|
318
|
+
}
|
|
319
|
+
// HEX value from XYBri
|
|
320
|
+
try {
|
|
321
|
+
const retRGB = hueColorConverter.ColorConverter.xyBriToRgb(oLight.color.xy.x, oLight.color.xy.y, oLight.dimming.brightness);
|
|
322
|
+
const ret = `#${hueColorConverter.ColorConverter.rgbHex(retRGB.r, retRGB.g, retRGB.b).toString()}`;
|
|
323
|
+
oLight.calculatedHEXColor = ret;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
oLight.calculatedHEXColor = undefined;
|
|
326
|
+
}
|
|
327
|
+
res.json(oLight);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`KNXUltimateHue: hueEngine: knxUltimateGetLightObject: error ${error.message}.`);
|
|
330
|
+
res.json({});
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
RED.httpAdmin.get("/KNXUltimateGetResourcesHUE", (req, res) => {
|
|
335
|
+
try {
|
|
336
|
+
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
337
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
338
|
+
if (serverId === null) {
|
|
339
|
+
RED.log.warn(`Warn KNXUltimateGetResourcesHUE serverId is null`);
|
|
340
|
+
res.json({ devices: `serverId not set` });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const jRet = serverId.getResources(req.query.rtype);
|
|
344
|
+
if (jRet !== undefined) {
|
|
345
|
+
res.json(jRet);
|
|
346
|
+
} else {
|
|
347
|
+
res.json({ devices: [{ name: "I'm still connecting...Try in some seconds" }] });
|
|
348
|
+
}
|
|
349
|
+
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
350
|
+
} catch (error) {
|
|
351
|
+
// RED.log.error(`Errore KNXUltimateGetResourcesHUE non gestito ${error.message}`);
|
|
352
|
+
res.json({ devices: error.message });
|
|
353
|
+
RED.log.error(`Err KNXUltimateGetResourcesHUE: ${error.message}`);
|
|
354
|
+
// (async () => {
|
|
355
|
+
// await node.initHUEConnection();
|
|
356
|
+
// })();
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
RED.httpAdmin.get("/knxUltimateDpts", (req, res) => {
|
|
361
|
+
try {
|
|
362
|
+
const dpts = Object.entries(dptlib).filter(onlyDptKeys).map(extractBaseNo).sort(sortBy("base"))
|
|
363
|
+
.reduce(toConcattedSubtypes, []);
|
|
364
|
+
res.json(dpts);
|
|
365
|
+
} catch (error) { }
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
// 15/09/2020 Supergiovane, read datapoint help usage
|
|
370
|
+
RED.httpAdmin.get("/knxUltimateDptsGetHelp", RED.auth.needsPermission("knxUltimate-config.read"), (req, res) => {
|
|
371
|
+
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
372
|
+
const sDPT = req.query.dpt.split(".")[0]; // Takes only the main type
|
|
373
|
+
let jRet;
|
|
374
|
+
if (sDPT === "0") {
|
|
375
|
+
// Special fake datapoint, meaning "Universal Mode"
|
|
376
|
+
jRet = {
|
|
377
|
+
help: `// KNX-Ultimate set as UNIVERSAL NODE
|
|
378
|
+
// Example of a function that sends a message to the KNX-Ultimate
|
|
379
|
+
msg.destination = "0/0/1"; // Set the destination
|
|
380
|
+
msg.payload = false; // issues a write or response (based on the options Telegram type above) to the KNX bus
|
|
381
|
+
msg.event = "GroupValue_Write"; // "GroupValue_Write" or "GroupValue_Response", overrides the option Telegram type above.
|
|
382
|
+
msg.dpt = "1.001"; // for example "1.001", overrides the Datapoint option. (Datapoints can be sent as 9 , "9" , "9.001" or "DPT9.001")
|
|
383
|
+
return msg;`,
|
|
384
|
+
helplink: "https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki",
|
|
385
|
+
};
|
|
386
|
+
res.json(jRet);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
jRet = {
|
|
390
|
+
help: "NO",
|
|
391
|
+
helplink: "https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/-SamplesHome",
|
|
392
|
+
};
|
|
393
|
+
const dpts = Object.entries(dptlib).filter(onlyDptKeys);
|
|
394
|
+
for (let index = 0; index < dpts.length; index++) {
|
|
395
|
+
if (dpts[index][0].toUpperCase() === `DPT${sDPT}`) {
|
|
396
|
+
jRet = {
|
|
397
|
+
help: dpts[index][1].basetype.hasOwnProperty("help") ? dpts[index][1].basetype.help : "NO",
|
|
398
|
+
helplink: dpts[index][1].basetype.hasOwnProperty("helplink")
|
|
399
|
+
? dpts[index][1].basetype.helplink
|
|
400
|
+
: "https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/-SamplesHome",
|
|
401
|
+
};
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
res.json(jRet);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
RED.httpAdmin.post("/banana", RED.auth.needsPermission("write"), (req, res) => {
|
|
409
|
+
const node = RED.nodes.getNode(req.params.id);
|
|
410
|
+
if (node != null) {
|
|
411
|
+
try {
|
|
412
|
+
if (req.body) {
|
|
413
|
+
console.log(body);
|
|
414
|
+
}
|
|
415
|
+
} catch (err) { }
|
|
416
|
+
}
|
|
417
|
+
res.json(req.body);
|
|
418
|
+
});
|
|
419
|
+
};
|
package/nodes/hue-config.html
CHANGED
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
{
|
|
28
28
|
text: "OK",
|
|
29
29
|
click: function (e) {
|
|
30
|
+
$("#mainWindow").hide();
|
|
31
|
+
$("#waitWindow").show();
|
|
30
32
|
// Send the infos to Supergiovane
|
|
31
|
-
$.getJSON("KNXUltimateRegisterToHueBridge?IP=" + $("#node-config-input-host").val(), (data) => {
|
|
33
|
+
$.getJSON("KNXUltimateRegisterToHueBridge?IP=" + $("#node-config-input-host").val() + "&serverId=" + this.id, (data) => {
|
|
32
34
|
this.value = "Connect";
|
|
33
35
|
this.disabled = false;
|
|
34
36
|
if (data.hasOwnProperty("error")) {
|
|
@@ -40,6 +42,8 @@
|
|
|
40
42
|
});
|
|
41
43
|
this.disabled = false;
|
|
42
44
|
$("#divDetails").hide()
|
|
45
|
+
$("#mainWindow").show();
|
|
46
|
+
$("#waitWindow").hide();
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -49,6 +53,8 @@
|
|
|
49
53
|
$("#node-config-input-username").val(data.user.username);
|
|
50
54
|
$("#node-config-input-clientkey").val(data.user.clientkey);
|
|
51
55
|
$("#node-config-input-bridgeid").val(data.bridge.data.bridgeid);
|
|
56
|
+
$("#mainWindow").show();
|
|
57
|
+
$("#waitWindow").hide();
|
|
52
58
|
$("#divDetails").show()
|
|
53
59
|
|
|
54
60
|
}).error(function (jqXHR, textStatus, errorThrown) {
|
|
@@ -58,6 +64,8 @@
|
|
|
58
64
|
fixed: false,
|
|
59
65
|
type: 'error'
|
|
60
66
|
})
|
|
67
|
+
$("#mainWindow").show();
|
|
68
|
+
$("#waitWindow").hide();
|
|
61
69
|
});
|
|
62
70
|
myNotification.close();
|
|
63
71
|
}
|
|
@@ -66,6 +74,8 @@
|
|
|
66
74
|
text: "CANCEL",
|
|
67
75
|
click: function (e) {
|
|
68
76
|
myNotification.close();
|
|
77
|
+
$("#mainWindow").show();
|
|
78
|
+
$("#waitWindow").hide();
|
|
69
79
|
}
|
|
70
80
|
}]
|
|
71
81
|
});
|
|
@@ -82,56 +92,62 @@
|
|
|
82
92
|
|
|
83
93
|
|
|
84
94
|
<script type="text/html" data-template-name="hue-config">
|
|
85
|
-
<div class="form-row">
|
|
86
|
-
<b><span data-i18n="hue-config.properties.title"></span></b>  <span style="color:red" data-i18n="[html]hue-config.properties.helplink"></span>
|
|
87
95
|
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<label for="node-config-input-host">
|
|
93
|
-
<i class="fa fa-server"></i> IP</label>
|
|
94
|
-
<input type="text" id="node-config-input-host" placeholder="Write here the HUE bridge's IP, then click CONNECT">
|
|
95
|
-
</div>
|
|
96
|
-
<div class="form-row">
|
|
97
|
-
<label><i class="fa fa-sign-in"></i> Register</label>
|
|
98
|
-
<input type="button" id="getinfocam" class="ui-button ui-corner-all ui-widget"
|
|
99
|
-
style="background-color:#AEE1FF;width:150px" value="CONNECT">
|
|
96
|
+
<div id="waitWindow" hidden>
|
|
97
|
+
<br/><br/><p align="center"><i class="fa-solid fa-hourglass-start fa-spin-pulse fa-4x"></i><br/><br/>
|
|
98
|
+
Wait, i'm talking to your HUE bridge...
|
|
99
|
+
</p>
|
|
100
100
|
</div>
|
|
101
|
-
|
|
102
|
-
<div id="divDetails" hidden>
|
|
103
|
-
<div class="form-row">
|
|
104
|
-
<label for="node-config-input-name">
|
|
105
|
-
<i class="fa fa-tag"></i>
|
|
106
|
-
<span data-i18n="hue-config.properties.node-config-input-name" </span>
|
|
107
|
-
</label>
|
|
108
|
-
<input type="text" id="node-config-input-name"><data-i18n="[Title]hue-config.properties.node-config-input-name"
|
|
109
|
-
style="margin-left:5px;">
|
|
110
|
-
</div>
|
|
111
101
|
|
|
102
|
+
<div id="mainWindow">
|
|
112
103
|
<div class="form-row">
|
|
113
|
-
|
|
114
|
-
<i class="fa fa-tag"></i>
|
|
115
|
-
Bridge ID
|
|
116
|
-
</label>
|
|
117
|
-
<input type="text" id="node-config-input-bridgeid" disabled>
|
|
118
|
-
</div>
|
|
104
|
+
<b><span data-i18n="hue-config.properties.title"></span></b>  <span style="color:red" data-i18n="[html]hue-config.properties.helplink"></span>
|
|
119
105
|
|
|
120
|
-
|
|
121
|
-
<label for="node-config-input-username"> Username</label>
|
|
122
|
-
<input type="password" id="node-config-input-username" placeholder="" disabled>
|
|
106
|
+
<p align='center'> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/huehub.jpg' width='40%'></p>
|
|
123
107
|
</div>
|
|
108
|
+
|
|
124
109
|
<div class="form-row">
|
|
125
|
-
<label for="node-config-input-
|
|
126
|
-
|
|
110
|
+
<label for="node-config-input-host">
|
|
111
|
+
<i class="fa fa-server"></i> IP</label>
|
|
112
|
+
<input type="text" id="node-config-input-host" placeholder="Write here the HUE bridge's IP, then click CONNECT">
|
|
127
113
|
</div>
|
|
128
114
|
<div class="form-row">
|
|
129
|
-
<label
|
|
130
|
-
<input
|
|
115
|
+
<label><i class="fa fa-sign-in"></i> Register</label>
|
|
116
|
+
<input type="button" id="getinfocam" class="ui-button ui-corner-all ui-widget"
|
|
117
|
+
style="background-color:#AEE1FF;width:150px" value="CONNECT">
|
|
131
118
|
</div>
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
119
|
|
|
120
|
+
<div id="divDetails" hidden>
|
|
121
|
+
<div class="form-row">
|
|
122
|
+
<label for="node-config-input-name">
|
|
123
|
+
<i class="fa fa-tag"></i>
|
|
124
|
+
<span data-i18n="hue-config.properties.node-config-input-name" </span>
|
|
125
|
+
</label>
|
|
126
|
+
<input type="text" id="node-config-input-name"><data-i18n="[Title]hue-config.properties.node-config-input-name"
|
|
127
|
+
style="margin-left:5px;">
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="form-row">
|
|
131
|
+
<label for="node-config-input-bridgeid">
|
|
132
|
+
<i class="fa fa-tag"></i>
|
|
133
|
+
Bridge ID
|
|
134
|
+
</label>
|
|
135
|
+
<input type="text" id="node-config-input-bridgeid" disabled>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="form-row">
|
|
139
|
+
<label for="node-config-input-username"> Username</label>
|
|
140
|
+
<input type="password" id="node-config-input-username" placeholder="" disabled>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="form-row">
|
|
143
|
+
<label for="node-config-input-clientkey"> Bridge Key</label>
|
|
144
|
+
<input type="password" id="node-config-input-clientkey" placeholder="" disabled>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
|
|
135
151
|
</script>
|
|
136
152
|
<script type="text/markdown" data-help-name="hue-config" This node registers to the Hue Bridge. Just set the Bridge's IP
|
|
137
153
|
and click **CONNECT** button. [Find it useful?](https://www.paypal.me/techtoday) <br />
|