node-red-contrib-knx-ultimate 2.0.1 → 2.0.2

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,6 +6,11 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ <p>
10
+ <b>Version 2.0.2</b> - June 2023<br/>
11
+ - NEW: HUE Button node. All HUE integrations are in BETA.<br/>
12
+ - Slowly integrating the help in the node-red help section.<br/>
13
+ </p>
9
14
  <p>
10
15
  <b>Version 2.0.1</b> - June 2023<br/>
11
16
  - NEW: more KNX group addresses in the HUE Light node<br/>
package/README.md CHANGED
@@ -35,7 +35,7 @@ msg.payload = {red:255, green:200, blue:30} // Put some colors in our life
35
35
  * **ALERTER node** [here](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/Alerter-Configuration). With the Alerter node you can signal to a display or to the node-red-contrib-tts-ultimate node (audio feedback), whenever the selected devices are alerted, i.e. they have payload **true**.
36
36
  * **LOAD CONTROL node** [here](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/LoadControl-Configuration). Control your loads (Oven, Washing machine, etc..) and avoit shutting down the main voltage due to too high power consumption.
37
37
  * **VIEWER node** [here](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/knxUltimateViewer). View all Group Addresses and values of your KNX BUS, in the Node-Red Dashboard.
38
- * **PHILIPS HUE nodeset** [here](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration). Link HUE lights and sensors to KNX in a simple way.
38
+ * **PHILIPS HUE nodeset** [here](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration). Link HUE devices to KNX in a simple way.
39
39
 
40
40
  <br>
41
41
  <br>
Binary file
Binary file
@@ -90,7 +90,7 @@ module.exports = (RED) => {
90
90
  })
91
91
  })
92
92
 
93
- // Endpoint for connecting to HUE Bridge
93
+ // Endpoint frontend
94
94
  RED.httpAdmin.get('/KNXUltimateGetAllLightsHUE', RED.auth.needsPermission('hue-config.read'), function (req, res) {
95
95
  try {
96
96
  (async () => {
@@ -110,7 +110,25 @@ module.exports = (RED) => {
110
110
  }
111
111
  })
112
112
 
113
-
113
+ RED.httpAdmin.get('/KNXUltimateGetDevicesHUE', RED.auth.needsPermission('hue-config.read'), function (req, res) {
114
+ try {
115
+ (async () => {
116
+ try {
117
+ // °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
118
+ const jRet = await node.hueManager.getDevices(req.query.rtype)
119
+ res.json(jRet)
120
+ // °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
121
+ } catch (err) {
122
+ RED.log.error('Errore KNXUltimategetButtonsHUE non gestito ' + err.message)
123
+ res.json({ error: err.message })
124
+ }
125
+ })()
126
+ } catch (err) {
127
+ RED.log.error('Errore KNXUltimategetButtons bsonto ' + err.message)
128
+ res.json({ error: err.message })
129
+ }
130
+ })
131
+
114
132
  node.addClient = (_Node) => {
115
133
  // Check if node already exists
116
134
  if (node.nodeClients.filter(x => x.id === _Node.id).length === 0) {
@@ -0,0 +1,586 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('knxUltimateHueButton', {
3
+ category: "KNX Ultimate",
4
+ color: '#C7E9C0',
5
+ defaults: {
6
+ //buttonState: {value: true},
7
+ server: { type: "knxUltimate-config", required: true },
8
+ serverHue: { type: "hue-config", required: true },
9
+ name: { value: "Hue Button (beta)" },
10
+
11
+ nameinitial_press: { value: "" },
12
+ GAinitial_press: { value: "" },
13
+ dptinitial_press: { value: "" },
14
+
15
+ namerepeat: { value: "" },
16
+ GArepeat: { value: "" },
17
+ dptrepeat: { value: "" },
18
+
19
+ nameshort_release: { value: "" },
20
+ GAshort_release: { value: "" },
21
+ dptshort_release: { value: "" },
22
+
23
+ namelong_release: { value: "" },
24
+ GAlong_release: { value: "" },
25
+ dptlong_release: { value: "" },
26
+
27
+ namedouble_short_release: { value: "" },
28
+ GAdouble_short_release: { value: "" },
29
+ dptdouble_short_release: { value: "" },
30
+
31
+ namelong_press: { value: "" },
32
+ GAlong_press: { value: "" },
33
+ dptlong_press: { value: "" },
34
+
35
+ toggleValues: { value: true },
36
+ hueDevice: { value: "" }
37
+ },
38
+ inputs: 0,
39
+ outputs: 0,
40
+ icon: "node-hue-icon.svg",
41
+ label: function () {
42
+ return (this.name);
43
+ },
44
+ paletteLabel: "Hue Button",
45
+ // button: {
46
+ // enabled: function() {
47
+ // // return whether or not the button is enabled, based on the current
48
+ // // configuration of the node
49
+ // return !this.changed
50
+ // },
51
+ // visible: function() {
52
+ // // return whether or not the button is visible, based on the current
53
+ // // configuration of the node
54
+ // return this.hasButton
55
+ // },
56
+ // //toggle: "buttonState",
57
+ // onclick: function() {}
58
+ // },
59
+ oneditprepare: function () {
60
+ var node = this;
61
+ var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
62
+ var oNodeServerHue = RED.nodes.node($("#node-input-serverHue").val()); // Store the config-node
63
+
64
+ // 19/02/2020 Used to get the server sooner als deploy.
65
+ $("#node-input-server").change(function () {
66
+ try {
67
+ oNodeServer = RED.nodes.node($(this).val());
68
+ } catch (error) { }
69
+ });
70
+ // 19/02/2020 Used to get the server sooner als deploy.
71
+ $("#node-input-serverHue").change(function () {
72
+ try {
73
+ oNodeServerHue = RED.nodes.node($(this).val());
74
+ } catch (error) { }
75
+ });
76
+
77
+ // 31/03/2020 Search Helper
78
+ function fullSearch(sourceText, searchString) {
79
+ // This searches for all words in a string
80
+ var aSearchWords = searchString.toLowerCase().split(" ");
81
+ var i = 0;
82
+ for (let index = 0; index < aSearchWords.length; index++) {
83
+ if (sourceText.toLowerCase().indexOf(aSearchWords[index]) > -1) i += 1;
84
+ }
85
+ return i == aSearchWords.length;
86
+ }
87
+
88
+ // DPT Switch command
89
+ // ########################
90
+ $.getJSON('knxUltimateDpts', (data) => {
91
+ data.forEach(dpt => {
92
+ if (dpt.value.startsWith("1.")) {
93
+ $("#node-input-dptinitial_press").append($("<option></option>")
94
+ .attr("value", dpt.value)
95
+ .text(dpt.text))
96
+ }
97
+ });
98
+ $("#node-input-dptinitial_press").val(this.dptinitial_press)
99
+ })
100
+
101
+ // Autocomplete suggestion with ETS csv File
102
+ $("#node-input-GAinitial_press").autocomplete({
103
+ minLength: 1,
104
+ source: function (request, response) {
105
+ //$.getJSON("csv", request, function( data, status, xhr ) {
106
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
107
+ response($.map(data, function (value, key) {
108
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
109
+ if (fullSearch(sSearch, request.term + " 1.")) {
110
+ return {
111
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
112
+ value: value.ga // Value
113
+ }
114
+ } else {
115
+ return null;
116
+ }
117
+ }));
118
+ });
119
+ }, select: function (event, ui) {
120
+ // Sets Datapoint and device name automatically
121
+ var sDevName = ui.item.label.split("#")[1].trim();
122
+ try {
123
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
124
+ } catch (error) {
125
+ }
126
+ $('#node-input-nameinitial_press').val(sDevName);
127
+ var optVal = $("#node-input-dptinitial_press option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
128
+ // Select the option value
129
+ $("#node-input-dptinitial_press").val(optVal);
130
+ }
131
+ });
132
+ // ########################
133
+
134
+
135
+
136
+ // DPT repeat
137
+ // ########################
138
+ $.getJSON('knxUltimateDpts', (data) => {
139
+ data.forEach(dpt => {
140
+ if (dpt.value.startsWith("1.") || dpt.value.startsWith("3.007")) {
141
+ $("#node-input-dptrepeat").append($("<option></option>")
142
+ .attr("value", dpt.value)
143
+ .text(dpt.text))
144
+ }
145
+ });
146
+
147
+ $("#node-input-dptrepeat").val(this.dptrepeat)
148
+ })
149
+
150
+ // Autocomplete suggestion with ETS csv File
151
+ $("#node-input-GArepeat").autocomplete({
152
+ minLength: 1,
153
+ source: function (request, response) {
154
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
155
+ response($.map(data, function (value, key) {
156
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
157
+ if (fullSearch(sSearch, request.term)) {
158
+ if (value.dpt.startsWith('1.') || value.dpt.startsWith('3.007')) {
159
+ return {
160
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
161
+ value: value.ga // Value
162
+ }
163
+ } else { return null; }
164
+ } else {
165
+ return null;
166
+ }
167
+ }));
168
+ });
169
+ }, select: function (event, ui) {
170
+ // Sets Datapoint and device name automatically
171
+ var sDevName = ui.item.label.split("#")[1].trim();
172
+ try {
173
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
174
+ } catch (error) {
175
+ }
176
+ $('#node-input-namerepeat').val(sDevName);
177
+ var optVal = $("#node-input-dptrepeat option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
178
+ // Select the option value
179
+ $("#node-input-dptrepeat").val(optVal);
180
+ }
181
+ });
182
+
183
+
184
+ // DPT dptshort_release
185
+ // ########################
186
+ $.getJSON('knxUltimateDpts', (data) => {
187
+ data.forEach(dpt => {
188
+ if (dpt.value.startsWith('1.')) {
189
+ $("#node-input-dptshort_release").append($("<option></option>")
190
+ .attr("value", dpt.value)
191
+ .text(dpt.text))
192
+ }
193
+ });
194
+
195
+ $("#node-input-dptshort_release").val(this.dptshort_release)
196
+
197
+ })
198
+
199
+ // Autocomplete suggestion with ETS csv File
200
+ $("#node-input-GAshort_release").autocomplete({
201
+ minLength: 1,
202
+ source: function (request, response) {
203
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
204
+ response($.map(data, function (value, key) {
205
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
206
+ if (fullSearch(sSearch, request.term + " 1.")) {
207
+ return {
208
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
209
+ value: value.ga // Value
210
+ }
211
+ } else {
212
+ return null;
213
+ }
214
+ }));
215
+ });
216
+ }, select: function (event, ui) {
217
+ // Sets Datapoint and device name automatically
218
+ var sDevName = ui.item.label.split("#")[1].trim();
219
+ try {
220
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
221
+ } catch (error) {
222
+ }
223
+ $('#node-input-nameshort_release').val(sDevName);
224
+ var optVal = $("#node-input-dptshort_release option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
225
+ // Select the option value
226
+ $("#node-input-dptshort_release").val(optVal);
227
+ }
228
+ });
229
+
230
+
231
+ // DPT dptlong_release and dptLightColorStateƒ
232
+ // ########################
233
+ $.getJSON('knxUltimateDpts', (data) => {
234
+ data.forEach(dpt => {
235
+ if (dpt.value.startsWith('1.')) {
236
+ $("#node-input-dptlong_release").append($("<option></option>")
237
+ .attr("value", dpt.value)
238
+ .text(dpt.text))
239
+ $("#node-input-dptdouble_short_release").append($("<option></option>")
240
+ .attr("value", dpt.value)
241
+ .text(dpt.text))
242
+ }
243
+ });
244
+
245
+ $("#node-input-dptlong_release").val(this.dptlong_release)
246
+ $("#node-input-dptdouble_short_release").val(this.dptdouble_short_release)
247
+
248
+ })
249
+
250
+ // Autocomplete suggestion with ETS csv File
251
+ $("#node-input-GAlong_release").autocomplete({
252
+ minLength: 1,
253
+ source: function (request, response) {
254
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
255
+ response($.map(data, function (value, key) {
256
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
257
+ if (fullSearch(sSearch, request.term + " 1.")) {
258
+ return {
259
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
260
+ value: value.ga // Value
261
+ }
262
+ } else {
263
+ return null;
264
+ }
265
+ }));
266
+ });
267
+ }, select: function (event, ui) {
268
+ // Sets Datapoint and device name automatically
269
+ var sDevName = ui.item.label.split("#")[1].trim();
270
+ try {
271
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
272
+ } catch (error) {
273
+ }
274
+ $('#node-input-namelong_release').val(sDevName);
275
+ var optVal = $("#node-input-dptlong_release option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
276
+ // Select the option value
277
+ $("#node-input-dptlong_release").val(optVal);
278
+ }
279
+ });
280
+
281
+ // Autocomplete suggestion with ETS csv File
282
+ $("#node-input-GAdouble_short_release").autocomplete({
283
+ minLength: 1,
284
+ source: function (request, response) {
285
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
286
+ response($.map(data, function (value, key) {
287
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
288
+ if (fullSearch(sSearch, request.term + " 1.")) {
289
+ return {
290
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
291
+ value: value.ga // Value
292
+ }
293
+ } else {
294
+ return null;
295
+ }
296
+ }));
297
+ });
298
+ }, select: function (event, ui) {
299
+ // Sets Datapoint and device name automatically
300
+ var sDevName = ui.item.label.split("#")[1].trim();
301
+ try {
302
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
303
+ } catch (error) {
304
+ }
305
+ $('#node-input-namedouble_short_release').val(sDevName);
306
+ var optVal = $("#node-input-dptdouble_short_release option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
307
+ // Select the option value
308
+ $("#node-input-dptdouble_short_release").val(optVal);
309
+ }
310
+ });
311
+
312
+
313
+
314
+ // DPT dptlong_press
315
+ // ########################
316
+ $.getJSON('knxUltimateDpts', (data) => {
317
+ data.forEach(dpt => {
318
+ if (dpt.value.startsWith('1.')) {
319
+ $("#node-input-dptlong_press").append($("<option></option>")
320
+ .attr("value", dpt.value)
321
+ .text(dpt.text))
322
+ }
323
+ });
324
+
325
+ $("#node-input-dptlong_press").val(this.dptlong_press)
326
+
327
+ })
328
+
329
+ // Autocomplete suggestion with ETS csv File
330
+ $("#node-input-GAlong_press").autocomplete({
331
+ minLength: 1,
332
+ source: function (request, response) {
333
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
334
+ response($.map(data, function (value, key) {
335
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
336
+ if (fullSearch(sSearch, request.term + " 1.")) {
337
+ return {
338
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
339
+ value: value.ga // Value
340
+ }
341
+ } else {
342
+ return null;
343
+ }
344
+ }));
345
+ });
346
+ }, select: function (event, ui) {
347
+ // Sets Datapoint and device name automatically
348
+ var sDevName = ui.item.label.split("#")[1].trim();
349
+ try {
350
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
351
+ } catch (error) {
352
+ }
353
+ $('#node-input-namelong_press').val(sDevName);
354
+ var optVal = $("#node-input-dptlong_press option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
355
+ // Select the option value
356
+ $("#node-input-dptlong_press").val(optVal);
357
+ }
358
+ });
359
+
360
+
361
+
362
+
363
+
364
+
365
+ // Autocomplete suggestion with HUE Lights
366
+ $("#node-input-name").autocomplete({
367
+ minLength: 1,
368
+ source: function (request, response) {
369
+ $.getJSON("KNXUltimateGetDevicesHUE?rtype=button&nodeID=" + oNodeServerHue.id, (data) => {
370
+ response($.map(data.devices, function (value, key) {
371
+ //alert(JSON.stringify(value) + " "+ key)
372
+ var sSearch = (value.name);
373
+ if (fullSearch(sSearch, request.term)) {
374
+ return {
375
+ hueDevice: value.id, // Label for Display
376
+ value: value.name // Value
377
+ }
378
+ } else {
379
+ return null;
380
+ }
381
+ }));
382
+ });
383
+ }, select: function (event, ui) {
384
+ // Sets the fields
385
+ $('#node-input-hueDevice').val(ui.item.hueDevice);
386
+ }
387
+ });
388
+
389
+
390
+ // ########################
391
+
392
+
393
+ },
394
+ oneditsave: function () {
395
+
396
+
397
+ },
398
+ oneditcancel: function () {
399
+
400
+ }
401
+ })
402
+
403
+ </script>
404
+
405
+ <script type="text/x-red" data-template-name="knxUltimateHueButton">
406
+
407
+
408
+ <div class="form-row">
409
+ <b>HUE Light</b>&nbsp&nbsp<span style="color:red"
410
+ &nbsp<i class="fa fa-question-circle"></i></span>
411
+ &nbsp<a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration"><u>Configuration</u></a>
412
+ <br />
413
+ <p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hueButton.png'></p>
414
+ <br />
415
+ <label for="node-input-server" >
416
+ <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img>
417
+ KNX GW
418
+ </label>
419
+ <input type="text" id="node-input-server" />
420
+ </div>
421
+
422
+ <div class="form-row">
423
+ <label for="node-input-serverHue">
424
+ <img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAABFUlEQVQ4EZWSsWoCQRCG1yiENEFEi6QSkjqWWoqFoBYJ+Br6JHkMn8Iibd4ihQpaJIhWNkry/ZtdGZY78Qa+m39nZ+dm9s4550awglNBluS/gVtAX6KgDclf68w2OThgfR9iT/jnoEv4TtByDThWTCDKW4SSZTf/zj9/eZbN+izTDuKGimu0vPF8B/YN8aC8LmcOj/AAn9CFTEs70Js/oGqy79C69bqJ5XbQI2kGO5N8QL9D08S8zBtBF5ZaVsznpCMoqJnVdjTpb1Db0fwIWmQV6BLXzFOYgA6/gDVfQN9bBWp2J2hdWDPoBV5FrKnAJutHikk/CHHR8i7x4iG7qQ720IYvu3GFbpHjx3pFrOFYkA354z/5bkK826phyAAAAABJRU5ErkJggg=="/>
425
+ HUE Bridge
426
+ </label>
427
+ <input type="text" id="node-input-serverHue" />
428
+ </div>
429
+
430
+ <br/>
431
+ <p>
432
+ <b>Philips HUE</b>
433
+ </p>
434
+
435
+ <div class="form-row">
436
+ <label for="node-input-hueDevice" >
437
+ <i class="fa fa-play-circle"></i>&nbspHue Button</label>
438
+ <input type="text" id="node-input-name" placeholder="Enter your hue device name" />
439
+ <input type="hidden" id="node-input-hueDevice" />
440
+ </div>
441
+
442
+ <br/>
443
+
444
+ <!-- <p> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/knx.png' width='32px'> -> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hue.png' width='32px'></p> -->
445
+
446
+ <p>
447
+ <b>PHILIPS HUE BUTTON EVENTS -> TO KNX</b>
448
+ </p>
449
+
450
+ <div class="form-row">
451
+ <label for="node-input-nameinitial_press" style="width:100px;"><i class="fa fa-play-circle-o"></i> Initial press</span></label>
452
+
453
+ <label for="node-input-GAinitial_press" style="width:20px;">GA</label>
454
+ <input type="text" id="node-input-GAinitial_press" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
455
+
456
+ <label for="node-input-dptinitial_press" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
457
+ <select id="node-input-dptinitial_press" style="width:140px;"></select>
458
+
459
+ <label for="node-input-nameinitial_press" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
460
+ <input type="text" id="node-input-nameinitial_press" style="width:200px;margin-left: 5px; text-align: left;">
461
+ </div>
462
+
463
+ <div class="form-row">
464
+ <label for="node-input-namerepeat" style="width:100px;"><i class="fa fa-play-circle-o"></i> Repeat</span></label>
465
+
466
+ <label for="node-input-GArepeat" style="width:20px;">GA</label>
467
+ <input type="text" id="node-input-GArepeat" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
468
+
469
+ <label for="node-input-dptrepeat" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
470
+ <select id="node-input-dptrepeat" style="width:140px;"></select>
471
+
472
+ <label for="node-input-namerepeat" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
473
+ <input type="text" id="node-input-namerepeat" style="width:200px;margin-left: 5px; text-align: left;">
474
+ </div>
475
+
476
+ <div class="form-row">
477
+ <label for="node-input-nameshort_release" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspShort release</label>
478
+
479
+ <label for="node-input-GAhort_release" style="width:20px;">GA</label>
480
+ <input type="text" id="node-input-GAshort_release" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
481
+
482
+ <label for="node-input-dptshort_release" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
483
+ <select id="node-input-dptshort_release" style="width:140px;"></select>
484
+
485
+ <label for="node-input-nameshort_release" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
486
+ <input type="text" id="node-input-nameshort_release" style="width:200px;margin-left: 5px; text-align: left;">
487
+ </div>
488
+
489
+ <div class="form-row">
490
+ <label for="node-input-namelong_release" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspLong release</label>
491
+
492
+ <label for="node-input-GAhort_release" style="width:20px;">GA</label>
493
+ <input type="text" id="node-input-GAlong_release" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
494
+
495
+ <label for="node-input-dptlong_release" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
496
+ <select id="node-input-dptlong_release" style="width:140px;"></select>
497
+
498
+ <label for="node-input-namelong_release" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
499
+ <input type="text" id="node-input-namelong_release" style="width:200px;margin-left: 5px; text-align: left;">
500
+ </div>
501
+
502
+ <div class="form-row">
503
+ <label for="node-input-namedouble_short_release" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspDouble short release</label>
504
+
505
+ <label for="node-input-GAdouble_short_release" style="width:20px;">GA</label>
506
+ <input type="text" id="node-input-GAdouble_short_release" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
507
+
508
+ <label for="node-input-dptdouble_short_release" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
509
+ <select id="node-input-dptdouble_short_release" style="width:140px;"></select>
510
+
511
+ <label for="node-input-namedouble_short_release" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueButton.node-input-name"></span></label>
512
+ <input type="text" id="node-input-namedouble_short_release" style="width:200px;margin-left: 5px; text-align: left;">
513
+ </div>
514
+
515
+ <div class="form-row">
516
+ <label for="node-input-namelong_press" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspLong press</label>
517
+
518
+ <label for="node-input-GAhort_release" style="width:20px;">GA</label>
519
+ <input type="text" id="node-input-GAlong_press" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
520
+
521
+ <label for="node-input-dptlong_press" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
522
+ <select id="node-input-dptlong_press" style="width:140px;"></select>
523
+
524
+ <label for="node-input-namelong_press" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
525
+ <input type="text" id="node-input-namelong_press" style="width:200px;margin-left: 5px; text-align: left;">
526
+ </div>
527
+
528
+ <div class="form-row">
529
+ <input type="checkbox" id="node-input-toggleValues" style="display:inline-block; width:auto; vertical-align:top;" />
530
+ <label style="width:auto" for="node-input-toggleValues">
531
+ <img
532
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAB7CAAAewgFu0HU+AAAFGmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjAtMDMtMjNUMTY6MjM6MjMrMDE6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIwLTAzLTIzVDE2OjI1OjM0KzAxOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIwLTAzLTIzVDE2OjI1OjM0KzAxOjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmJmNGM3NWVjLTIwNGYtNGY1YS05YTMxLTQ5NTU5YWJmZDE4NSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpiZjRjNzVlYy0yMDRmLTRmNWEtOWEzMS00OTU1OWFiZmQxODUiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpiZjRjNzVlYy0yMDRmLTRmNWEtOWEzMS00OTU1OWFiZmQxODUiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJmNGM3NWVjLTIwNGYtNGY1YS05YTMxLTQ5NTU5YWJmZDE4NSIgc3RFdnQ6d2hlbj0iMjAyMC0wMy0yM1QxNjoyMzoyMyswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIxLjEgKE1hY2ludG9zaCkiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+nhtLUgAAAE9JREFUKJG1UMsOACAIgub//7IdqvVQtjrExU1EEQLuiGCvgTNgl5D74MmVZPu4wIxQAgm+/sDec2VhgQPgf0sq1unjMlYJE/3MZrvy+kMFZQkZEWfC7ikAAAAASUVORK5CYII="></img>
533
+ Toggle values
534
+ </label>
535
+ </div>
536
+
537
+ </div>
538
+
539
+
540
+ <br/>
541
+ <br/>
542
+ <br/>
543
+ <br/>
544
+ <br/>
545
+ <br/>
546
+ <br/>
547
+ <br/>
548
+
549
+
550
+ </script>
551
+
552
+ <script type="text/markdown" data-help-name="knxUltimateHueButton">
553
+
554
+ [Find it useful?](https://www.paypal.me/techtoday)
555
+
556
+ This node lets you get the events of your HUE button.<br/>
557
+ There are many event you can choose from. The relevants one are *short release* and *repeat*.<br/>
558
+ In *short release* you can send true/false to your KNX group address to, for example, toggle a light.<br/>
559
+ In *repeat* event, you can DIM a KNX light.<br/>
560
+ The **toggle values** option is enabled by default. This option toggles the value of each KNX group address (*true/false, increase/decrease dim*))
561
+
562
+ **General**
563
+ |Property|Description|
564
+ |--|--|
565
+ | KNX GW | Select the KNX gateway to be used |
566
+ | HUE Bridge | Select the HUE Bridge to be used |
567
+ | Hue Button | HUE button to be used. The avaiable buttons start showing up while you're typing.|
568
+
569
+ <br/>
570
+
571
+ **PHILIPS HUE BUTTON EVENTS -> TO KNX**
572
+ Here you can get the HUE button events.<br/>
573
+ Start typing in the GA field, the name or group address of your KNX device, the avaiable devices start showing up while you're typing.
574
+
575
+ |Property|Description|
576
+ |--|--|
577
+ | Initial press | As soon as you press your HUE button, this event fires|
578
+ | Repeat | This command is used either to send DIM (increase/decrease) or true/false commands to the KNX group address |
579
+ | Short release | Rapid push of the button, fired as soon as you release your finger from the button. Usually used to toggle a KNX light true/false |
580
+ | Long release | Long press of the button, fired as soon as you release your finger from the button |
581
+ | Long press | Long press of the button, fired as soon as you long press the button |
582
+ | Toggle values | Enable or disable toggling values. If enabled, all values toggles, otherwise, all values are sent as *true* or *increase dim*, to the selected KNX group address. |
583
+
584
+
585
+ <br/>
586
+ </script>
@@ -0,0 +1,159 @@
1
+ module.exports = function (RED) {
2
+ const dptlib = require('./../KNXEngine/dptlib')
3
+ const hueColorConverter = require('./utils/hueColorConverter')
4
+
5
+
6
+ async function getLightState(node, _lightID) {
7
+ return new Promise((resolve, reject) => {
8
+ try {
9
+ if (node !== null && node.serverHue !== null && node.serverHue.hueManager !== null) {
10
+ node.serverHue.hueManager.getLight(_lightID).then(ret => {
11
+ node.currentHUEDevice = ret[0]
12
+ resolve(ret)
13
+ })
14
+ }
15
+ } catch (error) {
16
+ reject(error)
17
+ }
18
+ })
19
+ }
20
+
21
+ function knxUltimateHueButton(config) {
22
+ RED.nodes.createNode(this, config)
23
+ const node = this
24
+ node.server = RED.nodes.getNode(config.server)
25
+ node.serverHue = RED.nodes.getNode(config.serverHue)
26
+ node.topic = node.name
27
+ node.name = config.name === undefined ? 'Hue' : config.name
28
+ node.outputtopic = node.name
29
+ node.dpt = ''
30
+ node.notifyreadrequest = false
31
+ node.notifyreadrequestalsorespondtobus = 'false'
32
+ node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
33
+ node.notifyresponse = false
34
+ node.notifywrite = true
35
+ node.initialread = true
36
+ node.listenallga = true // Don't remove
37
+ node.outputtype = 'write'
38
+ node.outputRBE = false // Apply or not RBE to the output (Messages coming from flow)
39
+ node.inputRBE = false // Apply or not RBE to the input (Messages coming from BUS)
40
+ node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
41
+ node.passthrough = 'no'
42
+ node.formatmultiplyvalue = 1
43
+ node.formatnegativevalue = 'leave'
44
+ node.formatdecimalsvalue = 2
45
+ node.toggle1 = false
46
+ node.toggle2 = false // up or down if repeat field is set to DIM
47
+ node.toggle3 = false
48
+ node.toggle4 = false
49
+ node.toggle4 = false
50
+ node.toggle5 = false
51
+ node.toggle5 = false
52
+
53
+
54
+ // Read the state of the light and store it in the holding object
55
+ try {
56
+ if (config.hueLight !== undefined && config.hueLight !== '') getLightState(node, config.hueLight)
57
+ } catch (error) {
58
+ }
59
+
60
+
61
+
62
+ // Used to call the status update from the config node.
63
+ node.setNodeStatus = ({ fill, shape, text, payload }) => {
64
+
65
+ }
66
+
67
+ // This function is called by the knx-ultimate config node, to output a msg.payload.
68
+ node.handleSend = msg => {
69
+ }
70
+
71
+ node.handleSendHUE = _event => {
72
+ try {
73
+ if (_event.id === config.hueDevice) {
74
+ const knxMsgPayload = {}
75
+ if (_event.button.last_event === 'initial_press') {
76
+ knxMsgPayload.ga = config.GAinitial_press
77
+ knxMsgPayload.dpt = config.dptinitial_press
78
+ config.toggleValues ? node.toggle1 = !node.toggle1 : node.toggle1 = true
79
+ knxMsgPayload.payload = node.toggle1
80
+ // Toggle the DIM direction
81
+ config.toggleValues ? node.toggle2 = !node.toggle2 : node.toggle2 = true
82
+ }
83
+ if (_event.button.last_event === 'repeat') {
84
+ knxMsgPayload.ga = config.GArepeat
85
+ knxMsgPayload.dpt = config.dptrepeat
86
+ // True/False or DIM
87
+ if (knxMsgPayload.dpt.startsWith('1.')) {
88
+ config.toggleValues ? node.toggle2 = !node.toggle2 : node.toggle2 = true
89
+ knxMsgPayload.payload = node.toggle2
90
+ }
91
+ if (knxMsgPayload.dpt.startsWith('3.007')) {
92
+ if (!config.toggleValues) node.toggle2 = true
93
+ knxMsgPayload.payload = node.toggle2 ? { decr_incr: 1, data: 5 } : { decr_incr: 0, data: 5 }
94
+ }
95
+ }
96
+ if (_event.button.last_event === 'short_release') {
97
+ knxMsgPayload.ga = config.GAshort_release
98
+ knxMsgPayload.dpt = config.dptshort_release
99
+ config.toggleValues ? node.toggle3 = !node.toggle3 : node.toggle3 = true
100
+ knxMsgPayload.payload = node.toggle3
101
+ }
102
+ if (_event.button.last_event === 'long_release') {
103
+ knxMsgPayload.ga = config.GAlong_release
104
+ knxMsgPayload.dpt = config.dptlong_release
105
+ config.toggleValues ? node.toggle4 = !node.toggle4 : node.toggle4 = true
106
+ knxMsgPayload.payload = node.toggle4
107
+ }
108
+ if (_event.button.last_event === 'double_short_release') {
109
+ knxMsgPayload.ga = config.GAdouble_short_release
110
+ knxMsgPayload.dpt = config.dptdouble_short_release
111
+ config.toggleValues ? node.toggle5 = !node.toggle5 : node.toggle5 = true
112
+ knxMsgPayload.payload = node.toggle5
113
+ }
114
+ if (_event.button.last_event === 'long_press') {
115
+ knxMsgPayload.ga = config.GAlong_press
116
+ knxMsgPayload.dpt = config.dptlong_press
117
+ config.toggleValues ? node.toggle6 = !node.toggle6 : node.toggle6 = true
118
+ knxMsgPayload.payload = node.toggle6
119
+ }
120
+ // Send to KNX bus
121
+ if (knxMsgPayload.ga !== undefined) {
122
+ node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX ' + _event.button.last_event + ' ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
123
+ if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
124
+ }
125
+ }
126
+ } catch (error) {
127
+ node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
128
+ }
129
+ }
130
+
131
+ // On each deploy, unsubscribe+resubscribe
132
+ if (node.server) {
133
+ node.server.removeClient(node)
134
+ node.server.addClient(node)
135
+ }
136
+ if (node.serverHue) {
137
+ node.serverHue.removeClient(node)
138
+ node.serverHue.addClient(node)
139
+ }
140
+
141
+ node.on('input', function (msg) {
142
+
143
+ })
144
+
145
+ node.on('close', function (done) {
146
+ if (node.server) {
147
+ node.server.removeClient(node)
148
+ }
149
+ done()
150
+ })
151
+
152
+ // On each deploy, unsubscribe+resubscribe
153
+ if (node.server) {
154
+ node.server.removeClient(node)
155
+ node.server.addClient(node)
156
+ }
157
+ }
158
+ RED.nodes.registerType('knxUltimateHueButton', knxUltimateHueButton)
159
+ }
@@ -6,7 +6,7 @@
6
6
  //buttonState: {value: true},
7
7
  server: { type: "knxUltimate-config", required: true },
8
8
  serverHue: { type: "hue-config", required: true },
9
- name: { value: "Hue Light" },
9
+ name: { value: "Hue Light (beta)" },
10
10
 
11
11
  nameLightSwitch: { value: "" },
12
12
  GALightSwitch: { value: "" },
@@ -36,7 +36,7 @@
36
36
  GALightBrightnessState: { value: "" },
37
37
  dptLightBrightnessState: { value: "" },
38
38
 
39
- hueLight: { value: "" }
39
+ hueDevice: { value: "" }
40
40
  },
41
41
  inputs: 0,
42
42
  outputs: 0,
@@ -44,7 +44,7 @@
44
44
  label: function () {
45
45
  return (this.name);
46
46
  },
47
- paletteLabel: "Hue Light (beta)",
47
+ paletteLabel: "Hue Light",
48
48
  // button: {
49
49
  // enabled: function() {
50
50
  // // return whether or not the button is enabled, based on the current
@@ -62,6 +62,7 @@
62
62
  oneditprepare: function () {
63
63
  var node = this;
64
64
  var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
65
+ var oNodeServerHue = RED.nodes.node($("#node-input-serverHue").val()); // Store the config-node
65
66
 
66
67
  // 19/02/2020 Used to get the server sooner als deploy.
67
68
  $("#node-input-server").change(function () {
@@ -69,6 +70,12 @@
69
70
  oNodeServer = RED.nodes.node($(this).val());
70
71
  } catch (error) { }
71
72
  });
73
+ // 19/02/2020 Used to get the server sooner als deploy.
74
+ $("#node-input-serverHue").change(function () {
75
+ try {
76
+ oNodeServerHue = RED.nodes.node($(this).val());
77
+ } catch (error) { }
78
+ });
72
79
 
73
80
  // 31/03/2020 Search Helper
74
81
  function fullSearch(sourceText, searchString) {
@@ -391,17 +398,17 @@
391
398
 
392
399
 
393
400
  // Autocomplete suggestion with HUE Lights
394
- $("#node-input-hueLight").autocomplete({
401
+ $("#node-input-name").autocomplete({
395
402
  minLength: 1,
396
403
  source: function (request, response) {
397
- $.getJSON("KNXUltimateGetAllLightsHUE", (data) => {
398
- response($.map(data.lights, function (value, key) {
404
+ $.getJSON("KNXUltimateGetDevicesHUE?rtype=light&nodeID=" + oNodeServerHue.id, (data) => {
405
+ response($.map(data.devices, function (value, key) {
399
406
  //alert(JSON.stringify(value) + " "+ key)
400
- var sSearch = (value.metadata.name + " (" + value.metadata.archetype + " " + value.type + ") #" + value.id);
407
+ var sSearch = (value.name);
401
408
  if (fullSearch(sSearch, request.term)) {
402
409
  return {
403
- label: value.metadata.name + " (" + value.metadata.archetype + " " + value.type + ") #" + value.id, // Label for Display
404
- value: value.metadata.name + " (" + value.metadata.archetype + " " + value.type + ") #" + value.id // Value
410
+ hueDevice: value.id, // Label for Display
411
+ value: value.name // Value
405
412
  }
406
413
  } else {
407
414
  return null;
@@ -409,10 +416,8 @@
409
416
  }));
410
417
  });
411
418
  }, select: function (event, ui) {
412
- // Sets the field
413
- var sDevName = ui.item.label;
414
- $('#node-input-hueLight').val(sDevName);
415
- $('#node-input-name').val(sDevName.split("#")[0]);
419
+ // Sets the fields
420
+ $('#node-input-hueDevice').val(ui.item.hueDevice);
416
421
  }
417
422
  });
418
423
 
@@ -436,32 +441,27 @@
436
441
 
437
442
 
438
443
  <div class="form-row">
439
- <b><span data-i18n="knxUltimateHueLight.title"></span></b>&nbsp&nbsp<span style="color:red"
440
- data-i18n="[html]knxUltimateHueLight.helplink"></span>
444
+ <b>HUE Light</b>&nbsp&nbsp<span style="color:red"
445
+ &nbsp<i class="fa fa-question-circle"></i></span>
446
+ &nbsp<a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration"><u>Configuration</u></a>
441
447
  <br />
448
+ <p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hueLight.png'></p>
442
449
  <br />
443
450
  <label for="node-input-server" >
444
- <i class="fa fa-square-o"></i>
445
- <span data-i18n="knxUltimateHueLight.advanced.node-input-server"></span>
451
+ <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img>
452
+ KNX GW
446
453
  </label>
447
454
  <input type="text" id="node-input-server" />
448
455
  </div>
449
456
 
450
457
  <div class="form-row">
451
458
  <label for="node-input-serverHue">
452
- <i class="fa fa-lightbulb-o"></i>
453
- <span data-i18n="knxUltimateHueLight.advanced.node-input-serverHue"></span>
459
+ <img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAABFUlEQVQ4EZWSsWoCQRCG1yiENEFEi6QSkjqWWoqFoBYJ+Br6JHkMn8Iibd4ihQpaJIhWNkry/ZtdGZY78Qa+m39nZ+dm9s4550awglNBluS/gVtAX6KgDclf68w2OThgfR9iT/jnoEv4TtByDThWTCDKW4SSZTf/zj9/eZbN+izTDuKGimu0vPF8B/YN8aC8LmcOj/AAn9CFTEs70Js/oGqy79C69bqJ5XbQI2kGO5N8QL9D08S8zBtBF5ZaVsznpCMoqJnVdjTpb1Db0fwIWmQV6BLXzFOYgA6/gDVfQN9bBWp2J2hdWDPoBV5FrKnAJutHikk/CHHR8i7x4iG7qQ720IYvu3GFbpHjx3pFrOFYkA354z/5bkK826phyAAAAABJRU5ErkJggg=="/>
460
+ HUE Bridge
454
461
  </label>
455
462
  <input type="text" id="node-input-serverHue" />
456
463
  </div>
457
464
 
458
- <div class="form-row">
459
- <label for="node-input-name" >
460
- <i class="fa fa-tag"></i>
461
- <span data-i18n="knxUltimateHueLight.node-input-name"></span>
462
- </label>
463
- <input style="width:35%;" type="text" id="node-input-name" data-i18n="[placeholder]knxUltimateHueLight.node-input-name" />
464
- </div>
465
465
 
466
466
  <br/>
467
467
  <p>
@@ -469,9 +469,10 @@
469
469
  </p>
470
470
 
471
471
  <div class="form-row">
472
- <label for="node-input-hueLight" >
473
- <i class="fa fa-lightbulb-o"></i>&nbsp<span data-i18n="knxUltimateHueLight.node-input-hueLight"></span></label>
474
- <input type="text" id="node-input-hueLight" />
472
+ <label for="node-input-hueDevice" >
473
+ <i class="fa fa-play-circle"></i>&nbspName</label>
474
+ <input type="text" id="node-input-name" placeholder="Enter your hue device name"/>
475
+ <input type="hidden" id="node-input-hueDevice"/>
475
476
  </div>
476
477
 
477
478
  <br/>
@@ -496,7 +497,7 @@
496
497
  </div>
497
498
  </div>
498
499
  <div class="form-row">
499
- <label for="node-input-nameLightDIM" style="width:100px;"><i class="fa fa-play-circle-o"></i>Dimming</label>
500
+ <label for="node-input-nameLightDIM" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspDimming</label>
500
501
 
501
502
  <label for="node-input-GALightDIM" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
502
503
  <input type="text" id="node-input-GALightDIM" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
@@ -540,13 +541,13 @@
540
541
 
541
542
  <br/>
542
543
  <p>
543
- <b>PHILIPS HUE STATES -> TO KNX</b>
544
+ <b>PHILIPS HUE LIGHT STATE -> TO KNX</b>
544
545
  </p>
545
546
 
546
547
  <div class="form-row">
547
- <label for="node-input-nameLightState" style="width:100px;"><i class="fa fa-play-circle-o"></i> <span data-i18n="knxUltimateHueLight.node-input-nameLightState"></span></label>
548
+ <label for="node-input-nameLightState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Switch</label>
548
549
 
549
- <label for="node-input-GALightState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
550
+ <label for="node-input-GALightState" style="width:20px;">GA</label>
550
551
  <input type="text" id="node-input-GALightState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
551
552
 
552
553
  <label for="node-input-dptLightState" style="width:40px; margin-left: 0px; text-align: right;">
@@ -557,7 +558,7 @@
557
558
  <input type="text" id="node-input-nameLightState" style="width:200px;margin-left: 5px; text-align: left;">
558
559
  </div>
559
560
  <div class="form-row">
560
- <label for="node-input-nameLightColorState" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspColor State</label>
561
+ <label for="node-input-nameLightColorState" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspColor</label>
561
562
 
562
563
  <label for="node-input-GALightColorState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
563
564
  <input type="text" id="node-input-GALightColorState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
@@ -571,7 +572,7 @@
571
572
  </div>
572
573
  </div>
573
574
  <div class="form-row">
574
- <label for="node-input-nameLightBrightnessState" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspBrightness State</label>
575
+ <label for="node-input-nameLightBrightnessState" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspBrightness</label>
575
576
 
576
577
  <label for="node-input-GALightBrightnessState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
577
578
  <input type="text" id="node-input-GALightBrightnessState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
@@ -594,4 +595,46 @@
594
595
  <br/>
595
596
 
596
597
 
598
+ </script>
599
+
600
+
601
+ <script type="text/markdown" data-help-name="knxUltimateHueLight">
602
+
603
+ [Find it useful?](https://www.paypal.me/techtoday)
604
+
605
+ This node lets you control your Philips HUE light and also get the states of this lights.
606
+
607
+ **General**
608
+ |Property|Description|
609
+ |--|--|
610
+ | KNX GW | Select the KNX gateway to be used |
611
+ | HUE Bridge | Select the HUE Bridge to be used |
612
+ | Name | HUE light to be used. The avaiable lights start showing up while you're typing.|
613
+
614
+ <br/>
615
+
616
+ **KNX COMMANDS -> TO PHILIPS HUE**
617
+ Here you can choose the KNX addresses to be linked to the avaiable HUE light commands.<br/>
618
+ Start typing in the GA field, the name or group address of your KNX device, the avaiable devices start showing up while you're typing.
619
+
620
+ |Property|Description|
621
+ |--|--|
622
+ | Switch | This command is used to turn on/off the HUE light via a boolean KNX value true/false|
623
+ | Dimming | This command is used to DIM the HUE light via a KNX relative DIM datapoint |
624
+ | Color | This command is used to change the HUE light's color. Accepted datapoint is RGB triplet (r,g,b)|
625
+ | Brightness | This command is used to change the absolute HUE light's brightness |
626
+
627
+ <br/>
628
+
629
+ **PHILIPS HUE LIGHT STATE -> TO KNX**
630
+ Here you can link the state changes of your HUE light, to any KNX group address you want.<br/>
631
+ Start typing in the GA field, the name or group address of your KNX device, the avaiable devices start showing up while you're typing.
632
+
633
+ |Property|Description|
634
+ |--|--|
635
+ | Switch | This state is used to send on/off value to the selected KNX group address |
636
+ | Color | This state is used to send changes of the HUE light's color. Accepted datapoint is RGB triplet (r,g,b)|
637
+ | Brightness | This state is used to send changes of your absolute HUE light's brightness, to a KNX group address |
638
+
639
+ <br/>
597
640
  </script>
@@ -3,13 +3,15 @@ module.exports = function (RED) {
3
3
  const hueColorConverter = require('./utils/hueColorConverter')
4
4
 
5
5
 
6
- async function getLightState(node, config) {
6
+ async function getLightState(node, _lightID) {
7
7
  return new Promise((resolve, reject) => {
8
8
  try {
9
- node.serverHue.hueManager.getLight(config.hueLight.split('#')[1]).then(ret => {
10
- node.currentHUELight = ret[0]
11
- resolve(ret)
12
- })
9
+ if (node !== null && node.serverHue !== null && node.serverHue.hueManager !== null) {
10
+ node.serverHue.hueManager.getLight(_lightID).then(ret => {
11
+ node.currentHUEDevice = ret[0]
12
+ resolve(ret)
13
+ })
14
+ }
13
15
  } catch (error) {
14
16
  reject(error)
15
17
  }
@@ -45,7 +47,7 @@ module.exports = function (RED) {
45
47
 
46
48
  // Read the state of the light and store it in the holding object
47
49
  try {
48
- getLightState(node, config)
50
+ if (config.hueDevice !== undefined && config.hueDevice !== '') getLightState(node, config.hueDevice)
49
51
  } catch (error) {
50
52
  }
51
53
 
@@ -64,26 +66,26 @@ module.exports = function (RED) {
64
66
  case config.GALightSwitch:
65
67
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
66
68
  state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
67
- node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
69
+ node.serverHue.hueManager.setLightState(config.hueDevice, state)
68
70
  break
69
71
  case config.GALightDIM:
70
72
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
71
73
  state = msg.payload.decr_incr === 1 ? { dimming_delta: { action: 'up', brightness_delta: 20 } } : { dimming_delta: { action: 'down', brightness_delta: 20 } }
72
- node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
74
+ node.serverHue.hueManager.setLightState(config.hueDevice, state)
73
75
  break
74
76
  case config.GALightBrightness:
75
77
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
76
78
  state = { dimming: { brightness: msg.payload } }
77
- node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
79
+ node.serverHue.hueManager.setLightState(config.hueDevice, state)
78
80
  break
79
81
  case config.GALightColor:
80
82
  // Behavior like ISE HUE CONNECT, by setting the brightness and on/off as well
81
83
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColor))
82
- const gamut = node.currentHUELight.color.gamut_type || null
84
+ const gamut = node.currentHUEDevice.color.gamut_type || null
83
85
  const retXY = hueColorConverter.ColorConverter.rgbToXy(msg.payload.red, msg.payload.green, msg.payload.blue, gamut)
84
86
  const bright = hueColorConverter.ColorConverter.getBrightnessFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue)
85
87
  bright > 0 ? state = { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : state = { on: { on: false } }
86
- node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
88
+ node.serverHue.hueManager.setLightState(config.hueDevice, state)
87
89
  break
88
90
  default:
89
91
  break
@@ -96,7 +98,7 @@ module.exports = function (RED) {
96
98
 
97
99
  node.handleSendHUE = _event => {
98
100
  try {
99
- if (_event.id === config.hueLight.split('#')[1]) {
101
+ if (_event.id === config.hueDevice) {
100
102
  let knxMsgPayload = {}
101
103
  if (_event.hasOwnProperty('on')) {
102
104
  knxMsgPayload.ga = config.GALightState
@@ -106,7 +108,7 @@ module.exports = function (RED) {
106
108
  if (_event.hasOwnProperty('color')) {
107
109
  knxMsgPayload.ga = config.GALightColorState
108
110
  knxMsgPayload.dpt = config.dptLightColorState
109
- knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(_event.color.xy.x, _event.color.xy.y, node.currentHUELight.dimming.brightness)
111
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(_event.color.xy.x, _event.color.xy.y, node.currentHUEDevice.dimming.brightness)
110
112
  }
111
113
  if (_event.hasOwnProperty('dimming')) {
112
114
  knxMsgPayload.ga = config.GALightBrightnessState
@@ -116,7 +118,7 @@ module.exports = function (RED) {
116
118
  // Send to KNX bus
117
119
  if (knxMsgPayload.ga !== undefined) {
118
120
  node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX State ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
119
- if (config.GALightState !== '') node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
121
+ if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
120
122
  }
121
123
  }
122
124
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- //const hueApi = require('node-hue-api')
3
+ // const hueApi = require('node-hue-api')
4
4
  const hueApiV2 = require('node-hue')
5
5
  const { EventEmitter } = require('events')
6
6
 
@@ -16,28 +16,34 @@ class classHUE extends EventEmitter {
16
16
  this.hue = undefined
17
17
  }
18
18
 
19
- getAllLights = async () => {
19
+ // Get all devices and join it with relative rooms, by adding the room name to the device name
20
+ getDevices = async (_rtype) => {
20
21
  try {
21
22
  // V2
22
23
  const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
23
- const allLights = await hue.getLights()
24
- // allLights.forEach(light => {
25
- // console.log(JSON.stringify(light))
26
- // console.log("\n")
27
- // console.log("\n")
28
- // })
29
-
30
- // V2
31
- // const bridgeHUE = await hueApi.v3.api.createLocal(this.HUEBridgeIP).connect(this.username)
32
- // const allLights = await bridgeHUE.lights.getAll()
33
- // allLights.forEach(light => {
34
- // console.log(light.toStringDetailed())
35
- // })
36
-
37
- return { lights: allLights }
24
+ const retArray = []
25
+ const allResources = await hue.getResources()
26
+ const allRooms = await hue.getRooms()
27
+ const newArray = allResources.filter(x => x.type === _rtype)
28
+ // Add room name to the device name
29
+ newArray.forEach(device => {
30
+ // const Room = allRooms.find(room => {
31
+ // return room.children.find(child => child.rid === device.id)
32
+ // })
33
+ const Room = allRooms.find(room => room.children.find(child => child.rid === device.owner.rid))
34
+ const linkedDevName = allResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === device.id)).metadata.name || ''
35
+ if (_rtype === 'button') {
36
+ const controlID = device.metadata !== undefined ? (device.metadata.control_id || '') : ''
37
+ retArray.push({ name: linkedDevName + (controlID !== '' ? ', button ' + controlID : '') + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
38
+ }
39
+ if (_rtype === 'light') {
40
+ retArray.push({ name: linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
41
+ }
42
+ })
43
+ return { devices: retArray }
38
44
  } catch (error) {
39
- console.log('KNXUltimateHue: classHUE: error ' + error.message)
40
- return ({ lights: error.message })
45
+ console.log('KNXUltimateHue: HueUtils: classHUE: getDevices: error ' + error.message)
46
+ return ({ devices: error.message })
41
47
  }
42
48
  }
43
49
 
@@ -69,7 +75,7 @@ class classHUE extends EventEmitter {
69
75
  handleTheDog = async () => {
70
76
  this.timerWatchDog = setInterval(async () => {
71
77
  try {
72
- //const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
78
+ // const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
73
79
  if (this.hue !== undefined) {
74
80
  const sRet = await this.hue.getBridges()
75
81
  if (sRet.filter(e => e.bridge_id.toString().toLowerCase() === this.bridgeid.toString().toLowerCase()).length === 0) {
@@ -107,4 +113,3 @@ class classHUE extends EventEmitter {
107
113
  }
108
114
  }
109
115
  module.exports.classHUE = classHUE
110
-
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-knx-ultimate",
3
- "version": "2.0.1",
4
- "description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable.",
3
+ "version": "2.0.2",
4
+ "description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable. With integrated Philips HUE devices handling.",
5
5
  "dependencies": {
6
6
  "mkdirp": "1.0.4",
7
7
  "ping": "0.4.1",
@@ -29,7 +29,8 @@
29
29
  "knxUltimateLoadControl": "/nodes/knxUltimateLoadControl.js",
30
30
  "knxUltimateViewer": "/nodes/knxUltimateViewer.js",
31
31
  "hueConfig": "/nodes/hue-config.js",
32
- "knxUltimateHueLight": "/nodes/knxUltimateHueLight.js"
32
+ "knxUltimateHueLight": "/nodes/knxUltimateHueLight.js",
33
+ "knxUltimateHueButton": "/nodes/knxUltimateHueButton.js"
33
34
  }
34
35
  },
35
36
  "repository": {
@@ -1,10 +0,0 @@
1
- <script type="text/x-red" data-help-name="knxUltimateHueLight">
2
- <h1>KNX Ultimate - Nodo HUE</h1>
3
-
4
-
5
-
6
- <p>
7
- <a href="https://www.paypal.me/techtoday" target="_blank"><img src='https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square' width='30%'></a>
8
-
9
- </p>
10
- </script>