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.
@@ -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
+ };
@@ -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>&nbsp&nbsp<span style="color:red" data-i18n="[html]hue-config.properties.helplink"></span>
87
95
 
88
- <p align='center'> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/huehub.jpg' width='40%'></p>
89
-
90
-
91
- <div class="form-row">
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>&nbspRegister</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
- <label for="node-config-input-bridgeid">
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>&nbsp&nbsp<span style="color:red" data-i18n="[html]hue-config.properties.helplink"></span>
119
105
 
120
- <div class="form-row">
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-clientkey"> Bridge Key</label>
126
- <input type="password" id="node-config-input-clientkey" placeholder="" disabled>
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 for="node-config-input-debug"> Bridge Key</label>
130
- <input rows="20" style="width:100%" type="textarea" id="node-config-input-debug" placeholder="" disabled>
115
+ <label><i class="fa fa-sign-in"></i>&nbspRegister</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 />