node-red-contrib-knx-ultimate 2.2.29 → 2.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 2.2.31** - December 2023<br/>
10
+ - NEW: HUE Scene node: added the status GA and Datapoint, for the scene to send true/false if active/not active. This currently works only for "Single mode".<br/>
11
+ - WARNING: the new HUE Scene node is to be considered **BETA (= in testing with user feedback)**.<br/>
12
+
13
+
14
+ **Version 2.2.30** - December 2023<br/>
15
+ - NEW: HUE Scene node: added a "Multi scene" section, more powerful.<br/>
16
+ - HUE Scene: when selecting a group address for the scene, the scene number dropdown list doesn't show up.<br/>
17
+ - WARNING: the new HUE Light node is to be considered **RELEASED (= production ready, but please report anyway any issue)**.<br/>
18
+ - WARNING: the new HUE Scene node is to be considered **BETA (= in testing with user feedback)**.<br/>
19
+
9
20
  **Version 2.2.29** - November 2023<br/>
10
21
  This is an interim version, to quick fix some issues. Please report any issue with HUE Nodes, on gitHub.<br/>
11
22
  - HUE Light: fixed an issue causing the node status to signal an error. Filtered the groupvalue_read from imbound KNX messages.<br/>
package/README.md CHANGED
@@ -13,7 +13,9 @@
13
13
 
14
14
  ![Sample Node](img/readmemain.png)
15
15
 
16
- Control your KNX intallation via Node-Red!
16
+ Control your KNX intallation via Node-Red!<br/>
17
+ A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer.<br/>
18
+ Easy to use and highly configurable.
17
19
 
18
20
  **You can use it immediately!**
19
21
 
@@ -331,7 +333,7 @@ List of commercial companies, which have given us permission to be mentioned on
331
333
  * [![](https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/c/knxuserforum.png)](https://knx-user-forum.de/forum/öffentlicher-bereich/knx-eib-forum/1389088-knx-node-for-node-red)
332
334
 
333
335
  **China**
334
- * [QQ group: 837579219 (加群需要备注 “来自github](tencent://groupwpa/?subcmd=all&param=7b2267726f757055696e223a3833373537393231392c2274696d655374616d70223a313633303934363639312c22617574684b6579223a22762b72482b466f4a496a75613033794e4a30744a6970756c55753639424f4d55724f464c4a6c474b77346a30326b7a4f7a3338535536517844684d7756414d62222c2261757468223a22227d&jump_from=)
336
+ * [QQ group: 837579219 (加群需要备注 "来自github"](tencent://groupwpa/?subcmd=all&param=7b2267726f757055696e223a3833373537393231392c2274696d655374616d70223a313633303934363639312c22617574684b6579223a22762b72482b466f4a496a75613033794e4a30744a6970756c55753639424f4d55724f464c4a6c474b77346a30326b7a4f7a3338535536517844684d7756414d62222c2261757468223a22227d&jump_from=)
335
337
 
336
338
 
337
339
 
@@ -167,7 +167,7 @@
167
167
  <br />
168
168
  <br />
169
169
  <p align="center">
170
- <i class="fa-solid fa-battery-half fa-beat-fade fa-8x"></i>
170
+ <i class="fa-solid fa-battery-half fa-beat-fade fa-4x"></i>
171
171
  </p>
172
172
  <br />
173
173
  <label for="node-input-server" >
@@ -312,7 +312,7 @@
312
312
  <br />
313
313
  <br />
314
314
  <p align="center">
315
- <i class="fa-regular fa-circle-dot fa-beat-fade fa-8x"></i>
315
+ <i class="fa-regular fa-circle-dot fa-beat-fade fa-4x"></i>
316
316
  </p>
317
317
  <br />
318
318
  <label for="node-input-server" >
@@ -651,7 +651,7 @@
651
651
  label: function () {
652
652
  return this.name;
653
653
  },
654
- paletteLabel: "Hue Light (beta)",
654
+ paletteLabel: "Hue Light",
655
655
  oneditprepare: function () {
656
656
  onEditPrepare(this);
657
657
  },
@@ -758,7 +758,7 @@
758
758
  <br />
759
759
  <br />
760
760
  <p align="center">
761
- <i class="fa-regular fa-lightbulb fa-bounce fa-8x"></i>
761
+ <i class="fa-regular fa-lightbulb fa-bounce fa-4x"></i>
762
762
  </p>
763
763
  <br />
764
764
  <label for="node-input-server">
@@ -1154,7 +1154,7 @@
1154
1154
  </div>
1155
1155
  <div class="form-row">
1156
1156
  <label for="node-input-enableNodePINS" style="width:260px;">
1157
- <i class="fa fa-square-o"></i> Node Input/Output PINs
1157
+ <i class="fa fa-circle"></i> Node Input/Output PINs
1158
1158
  </label>
1159
1159
  <select id="node-input-enableNodePINS">
1160
1160
  <option value="no">Hide</option>
@@ -165,7 +165,7 @@
165
165
  <br />
166
166
  <br />
167
167
  <p align="center">
168
- <i class="fa-solid fa-circle-half-stroke fa-8x"></i>
168
+ <i class="fa-solid fa-circle-half-stroke fa-4x"></i>
169
169
  </p>
170
170
  <br />
171
171
  <label for="node-input-server" >
@@ -164,7 +164,7 @@
164
164
  <br />
165
165
  <br />
166
166
  <p align="center">
167
- <i class="fa-solid fa-person-walking fa-shake fa-8x"></i>
167
+ <i class="fa-solid fa-person-walking fa-shake fa-4x"></i>
168
168
  </p>
169
169
  <br />
170
170
  <label for="node-input-server" >
@@ -43,8 +43,6 @@ module.exports = function (RED) {
43
43
  if (_event.id === config.hueDevice) {
44
44
 
45
45
  if (!_event.hasOwnProperty("motion") || _event.motion.motion === undefined) return;
46
-
47
-
48
46
  const knxMsgPayload = {};
49
47
  knxMsgPayload.topic = config.GAmotion;
50
48
  knxMsgPayload.dpt = config.dptmotion;
@@ -10,25 +10,39 @@
10
10
  serverHue: { type: "hue-config", required: true },
11
11
  name: { value: "" },
12
12
 
13
+ // Single scene
13
14
  namescene: { value: "" },
14
15
  GAscene: { value: "" },
15
16
  dptscene: { value: "" },
16
17
  valscene: { value: 0 }, // the scene number or true/false
18
+ namesceneStatus: { value: "" },
19
+ GAsceneStatus: { value: "" },
20
+ dptsceneStatus: { value: "" },
17
21
 
18
22
  enableNodePINS: { value: "no" },
19
23
  outputs: { value: 0 },
20
24
  inputs: { value: 0 },
21
25
 
22
26
  hueDevice: { value: "" },
23
- hueSceneRecallType: { value: "active" }
27
+ hueSceneRecallType: { value: "active" },
28
+
29
+ // Multi scene
30
+ GAsceneMulti: { value: "" },
31
+ namesceneMulti: { value: "" },
32
+ dptsceneMulti: { value: "" },
33
+ rules: { value: [{ t: "eq", v: "", vt: "str" }] },
34
+ selectedModeTabNumber: { value: 0 }
35
+
24
36
  },
25
37
  inputs: 0,
26
38
  outputs: 0,
27
39
  icon: "node-hue-icon.svg",
28
40
  label: function () {
29
- return (this.name);
41
+ if (this.selectedModeTabNumber === undefined) return this.name;
42
+ if (Number(this.selectedModeTabNumber) === 0) return this.name;
43
+ if (Number(this.selectedModeTabNumber) === 1) return this.namesceneMulti;
30
44
  },
31
- paletteLabel: "Hue Scene",
45
+ paletteLabel: "Hue Scene (Beta)",
32
46
  // button: {
33
47
  // enabled: function() {
34
48
  // // return whether or not the button is enabled, based on the current
@@ -89,8 +103,20 @@
89
103
  .attr("value", dpt.value)
90
104
  .text(dpt.text))
91
105
  }
106
+ if (dpt.value.startsWith("18.")) {
107
+ $("#node-input-dptsceneMulti").append($("<option></option>")
108
+ .attr("value", dpt.value)
109
+ .text(dpt.text))
110
+ }
111
+ if (dpt.value.startsWith("1.")) {
112
+ $("#node-input-dptsceneStatus").append($("<option></option>")
113
+ .attr("value", dpt.value)
114
+ .text(dpt.text))
115
+ }
92
116
  });
93
117
  $("#node-input-dptscene").val(this.dptscene)
118
+ $("#node-input-dptsceneStatus").val(this.dptsceneStatus)
119
+ $("#node-input-dptsceneMulti").val(this.dptsceneMulti)
94
120
  ShowHideValScene();
95
121
  })
96
122
 
@@ -125,6 +151,73 @@
125
151
  var optVal = $("#node-input-dptscene option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
126
152
  // Select the option value
127
153
  $("#node-input-dptscene").val(optVal);
154
+ ShowHideValScene();
155
+ }
156
+ });
157
+ $("#node-input-GAsceneStatus").autocomplete({
158
+ minLength: 1,
159
+ source: function (request, response) {
160
+ //$.getJSON("csv", request, function( data, status, xhr ) {
161
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
162
+ response($.map(data, function (value, key) {
163
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
164
+ if (fullSearch(sSearch, request.term)) {
165
+ if (value.dpt.startsWith("1.")) {
166
+ return {
167
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
168
+ value: value.ga // Value
169
+ }
170
+ } else { return null; }
171
+ } else {
172
+ return null;
173
+ }
174
+ }));
175
+ });
176
+ }, select: function (event, ui) {
177
+ // Sets Datapoint and device name automatically
178
+ var sDevName = ui.item.label.split("#")[1].trim();
179
+ try {
180
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
181
+ } catch (error) {
182
+ }
183
+ $('#node-input-namesceneStatus').val(sDevName);
184
+ var optVal = $("#node-input-dptsceneStatus option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
185
+ // Select the option value
186
+ $("#node-input-dptsceneStatus").val(optVal);
187
+ ShowHideValScene();
188
+ }
189
+ });
190
+ $("#node-input-GAsceneMulti").autocomplete({
191
+ minLength: 1,
192
+ source: function (request, response) {
193
+ //$.getJSON("csv", request, function( data, status, xhr ) {
194
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
195
+ response($.map(data, function (value, key) {
196
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
197
+ if (fullSearch(sSearch, request.term)) {
198
+ if (value.dpt.startsWith("18.")) {
199
+ return {
200
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
201
+ value: value.ga // Value
202
+ }
203
+ } else { return null; }
204
+ } else {
205
+ return null;
206
+ }
207
+ }));
208
+ });
209
+ }, select: function (event, ui) {
210
+ // Sets Datapoint and device name automatically
211
+ var sDevName = ui.item.label.split("#")[1].trim();
212
+ try {
213
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
214
+ } catch (error) {
215
+ }
216
+ $('#node-input-namesceneMulti').val(sDevName);
217
+ var optVal = $("#node-input-dptsceneMulti option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
218
+ // Select the option value
219
+ $("#node-input-dptsceneMulti").val(optVal);
220
+ ShowHideValScene();
128
221
  }
129
222
  });
130
223
  // ########################
@@ -141,6 +234,7 @@
141
234
  }
142
235
  }
143
236
  }
237
+
144
238
  $("#node-input-dptscene").on("change", function () {
145
239
  ShowHideValScene()
146
240
  });
@@ -169,8 +263,138 @@
169
263
  $('#node-input-hueDevice').val(ui.item.hueDevice);
170
264
  }
171
265
  });
172
- // ########################
266
+ // ########################
267
+
268
+
269
+
270
+ // -----------------------------------------------------------------------
271
+ // MULTI SCENE
272
+ // ########################
273
+ function resizeRule(rule) { }
274
+ function setTableTitle(_selectedIndex) {
275
+ // Save only the tab 0 or 1
276
+ if (_selectedIndex === undefined) _selectedIndex = 0;
277
+ if (Number(_selectedIndex) <= 1) {
278
+ $("#node-input-selectedModeTabNumber").val(Number(_selectedIndex));
279
+ if (Number(_selectedIndex) === 0) {
280
+ $('#tabs ul:first li:eq(0) a').html('<i class="fa fa-check" aria-hidden="true"></i> Single mode');
281
+ $('#tabs ul:first li:eq(1) a').text('Multi mode');
282
+ } else if (Number(_selectedIndex) === 1) {
283
+ $('#tabs ul:first li:eq(0) a').text('Sigle mode');
284
+ $('#tabs ul:first li:eq(1) a').html('<i class="fa fa-check" aria-hidden="true"></i> Multi mode');
285
+ }
286
+ }
287
+ }
288
+
289
+ $("#node-input-rule-container").css('min-height', '200px').css('min-width', '450px').editableList({
290
+ scrollOnAdd: true,
291
+ //header: $("<div>").append($.parseHTML("<div style='width:5%; display: inline-grid'>Sort</div><div style='width:15%; display: inline-grid'>KNX Scene number</div><div style='width:60%; display: inline-grid'>HUE Scene name</div><div style='width:15%; display: inline-grid'>Recall scene as</div><div style='width:5%; display: inline-grid'>Delete</div>")),
292
+ addItem: function (container, i, opt) { // row, index, data
293
+ // opt.r is: { rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID:rowRuleHUESceneID, rowRuleRecallAs:rowRuleRecallAs}
294
+
295
+ if (!opt.hasOwnProperty('r')) {
296
+ opt.r = {};
297
+ }
298
+ var rule = opt.r;
299
+ if (!opt.hasOwnProperty('i')) {
300
+ opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString();
301
+ }
302
+
303
+ container.css({
304
+ overflow: 'hidden',
305
+ whiteSpace: 'nowrap'
306
+ });
307
+
308
+ var row = $('<div class="form-row"/>').appendTo(container);
309
+ var rowRuleKNXSceneNumber = $('<select/>', { class: "rowRuleKNXSceneNumber", type: "text", style: "width:25%; margin-left: 5px; text-align: left;" }).appendTo(row);
310
+ var rowRuleHUESceneName = $("<input/>", { class: "rowRuleHUESceneName", type: "text", placeholder: "HUE device name", style: "width:45%; margin-left: 5px; text-align: left;" }).appendTo(row);
311
+ var rowRuleHUESceneID = $("<input/>", { class: "rowRuleHUESceneID", type: "hidden", placeholder: "HUE device name", style: "width:0px; margin-left: 5px; text-align: left;" }).appendTo(row);
312
+ var rowRuleRecallAs = $('<select/>', { class: "rowRuleRecallAs", type: "text", style: "width:25%; margin-left: 5px; text-align: left;" }).appendTo(row);
313
+ var finalspan = $('<span/>', { style: "" }).appendTo(row);
314
+ finalspan.append('<span class="node-input-rule-index"></span> ');
315
+
316
+ for (let index = 1; index < 64; index++) {
317
+ rowRuleKNXSceneNumber.append(
318
+ $("<option>")
319
+ .val(index)
320
+ .text("KNX Scene n." + index.toString())
321
+ );
322
+ rowRuleKNXSceneNumber.val(rule.rowRuleKNXSceneNumber);
323
+ }
324
+ rowRuleRecallAs.append(
325
+ $("<option>")
326
+ .val("active")
327
+ .text("Recall as Active")
328
+ );
329
+ rowRuleRecallAs.append(
330
+ $("<option>")
331
+ .val("dynamic_palette")
332
+ .text("Recall as Dynamic")
333
+ );
334
+ rowRuleRecallAs.append(
335
+ $("<option>")
336
+ .val("static")
337
+ .text("Recall as Static")
338
+ );
339
+ rowRuleRecallAs.val(rule.rowRuleRecallAs);
340
+ rowRuleHUESceneName.autocomplete({
341
+ minLength: 1,
342
+ source: function (request, response) {
343
+ $.getJSON("KNXUltimateGetResourcesHUE?rtype=scene&nodeID=" + oNodeServerHue.id, (data) => {
344
+ response($.map(data.devices, function (value, key) {
345
+ //alert(JSON.stringify(value) + " "+ key)
346
+ var sSearch = (value.name);
347
+ if (fullSearch(sSearch, request.term)) {
348
+ return {
349
+ hueDevice: value.id, // Label for Display
350
+ value: value.name // Value
351
+ }
352
+ } else {
353
+ return null;
354
+ }
355
+ }));
356
+ });
357
+ }, select: function (event, ui) {
358
+ // Sets Datapoint and device name automatically
359
+ rowRuleHUESceneID.val(ui.item.hueDevice);
360
+ }
361
+ });
362
+ rowRuleRecallAs.val(rule.rowRuleRecallAs)
363
+ rowRuleHUESceneName.val(rule.rowRuleHUESceneName);
364
+ rowRuleHUESceneID.val(rule.rowRuleHUESceneID);
365
+ },
366
+ removeItem: function (opt) {
367
+
368
+ },
369
+ resizeItem: resizeRule,
370
+ sortItems: function (rules) {
371
+ },
372
+ sortable: true,
373
+ removable: true
374
+ });
173
375
 
376
+ // Put some spaces after the container
377
+ $('<br/><br/><br/>').insertAfter($("#node-input-rule-container"));
378
+
379
+ // For each rule, create a row
380
+ if (this.rules !== undefined) {
381
+ for (var i = 0; i < this.rules.length; i++) {
382
+ var rule = this.rules[i];
383
+ $("#node-input-rule-container").editableList('addItem', { r: rule, i: i });
384
+ }
385
+ }
386
+
387
+ // ########################
388
+ // MULTI SCENE
389
+ // -----------------------------------------------------------------------
390
+ this.selectedModeTabNumber === undefined ? 0 : this.selectedModeTabNumber;
391
+ $("#tabs").tabs({
392
+ activate: function (event, ui) {
393
+ setTableTitle(ui.newTab.index());
394
+ }
395
+ });
396
+ $("#tabs").tabs("option", "active", this.selectedModeTabNumber);
397
+ setTableTitle(this.selectedModeTabNumber);
174
398
  },
175
399
  oneditsave: function () {
176
400
  if ($("#node-input-enableNodePINS").val() === "yes") {
@@ -181,9 +405,24 @@
181
405
  this.inputs = 0;
182
406
  }
183
407
 
408
+ var node = this;
409
+ // opt.r is: { rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID:rowRuleHUESceneID, rowRuleRecallAs:rowRuleRecallAs}
410
+ var rules = $("#node-input-rule-container").editableList('items');
411
+ node.rules = [];
412
+ rules.each(function (i) {
413
+ var rule = $(this);
414
+ var rowRuleKNXSceneNumber = rule.find(".rowRuleKNXSceneNumber").val();
415
+ var rowRuleHUESceneName = rule.find(".rowRuleHUESceneName").val();
416
+ var rowRuleHUESceneID = rule.find(".rowRuleHUESceneID").val();
417
+ var rowRuleRecallAs = rule.find(".rowRuleRecallAs").val();
418
+ node.rules.push({ rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID: rowRuleHUESceneID, rowRuleRecallAs: rowRuleRecallAs });
419
+ });
184
420
  },
185
421
  oneditcancel: function () {
186
422
 
423
+ },
424
+ oneditresize: function (size) {
425
+
187
426
  }
188
427
  })
189
428
 
@@ -191,14 +430,14 @@
191
430
 
192
431
  <script type="text/html" data-template-name="knxUltimateHueScene">
193
432
 
194
-
195
433
  <div class="form-row">
434
+ <input type="hidden" id="node-input-selectedModeTabNumber">
196
435
  <b>HUE Scene node</b>&nbsp&nbsp<span style="color:red"
197
436
  &nbsp<i class="fa fa-youtube"></i></span>&nbsp<a target="_blank" href="https://youtu.be/jjEUI1J8bkA"><u>Youtube sample</u></a>
198
437
  <br />
199
438
  <br />
200
439
  <p align="center">
201
- <i class="fa fa-tv fa-fade fa-8x"></i>
440
+ <i class="fa fa-tv fa-fade fa-4x"></i>
202
441
  </p>
203
442
  <br />
204
443
  <label for="node-input-server" >
@@ -217,96 +456,161 @@
217
456
  </div>
218
457
 
219
458
  <br/>
220
- <p>
221
- <b>Philips HUE</b>
222
- </p>
459
+ <div id="tabs">
223
460
 
224
- <div class="form-row">
225
- <label for="node-input-hueDevice" >
226
- <i class="fa fa-play-circle"></i>&nbspHue Scene</label>
227
- <input type="text" id="node-input-name" placeholder="Enter your hue device name" />
228
- <input type="hidden" id="node-input-hueDevice" />
229
- </div>
230
- <div class="form-row">
231
- <label for="node-input-hueSceneRecallType">
232
- <i class="fa fa-minus-circle"></i> Recall as
233
- </label>
234
- <select id="node-input-hueSceneRecallType">
235
- <option value="active">Active</option>
236
- <option value="dynamic_palette">Dynamic</option>
237
- <option value="static">Static</option>
238
- </select>
239
- </div>
240
-
241
- <br/>
242
-
243
- <p>
244
- <b>KNX</b>
245
- </p>
246
-
247
- <div class="form-row">
248
- <label for="node-input-namescene" style="width:100px;"><i class="fa fa-play-circle-o"></i> Recall</label>
249
-
250
- <label for="node-input-GAscene" style="width:20px;">GA</label>
251
- <input type="text" id="node-input-GAscene" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
252
-
253
- <label for="node-input-dptscene" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
254
- <select id="node-input-dptscene" style="width:140px;"></select>
255
-
256
- <label for="node-input-namescene" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
257
- <input type="text" id="node-input-namescene" style="width:200px;margin-left: 5px; text-align: left;">
258
- </div>
259
-
260
- <div class="form-row" id="divValScene" hidden>
261
- <label for="node-input-valscene" style="width:100px;"></label>
262
- <label for="node-input-valscene" style="width:20px;">#</label>
263
- <select id="node-input-valscene" style="width:180px;margin-left: 5px; text-align: left;"></select>
264
- </div>
265
-
266
- <br/>
267
- <br/>
268
-
269
- <p>
270
- <b>BEHAVIOUR</b>
271
- </p>
272
-
273
- <div class="form-row">
274
- <label for="node-input-enableNodePINS" style="width:240px;">
275
- <i class="fa fa-square-o"></i> Node Input/Output PINs
276
- </label>
277
- <select id="node-input-enableNodePINS">
278
- <option value="no">Hide</option>
279
- <option value="yes">Show node input/output PINs</option>
280
- </select>
281
-
282
- <br/>
283
- <br/>
284
- <br/>
285
-
461
+ <ul>
462
+ <li><a href="#tabs-1"> Single scene</a></li>
463
+ <li><a href="#tabs-2"> Multi scene</a></li>
464
+ <li><a href="#tabs-3"> Behaviour</a></li>
465
+ </ul>
466
+ <div id="tabs-1">
467
+ <br/>
468
+ <p>
469
+ <b>Philips HUE</b>
470
+ </p>
471
+
472
+ <div class="form-row">
473
+ <label for="node-input-hueDevice" >
474
+ <i class="fa fa-play-circle"></i>&nbspHue Scene</label>
475
+ <input type="text" id="node-input-name" placeholder="Enter your hue device name" />
476
+ <input type="hidden" id="node-input-hueDevice" />
477
+ </div>
478
+ <div class="form-row">
479
+ <label for="node-input-hueSceneRecallType">
480
+ <i class="fa fa-minus-circle"></i> Recall as
481
+ </label>
482
+ <select id="node-input-hueSceneRecallType">
483
+ <option value="active">Recall as Active</option>
484
+ <option value="dynamic_palette">Recall as Dynamic</option>
485
+ <option value="static">Recall as Static</option>
486
+ </select>
487
+ </div>
488
+
489
+ <br/>
490
+ <p>
491
+ <b>KNX</b>
492
+ </p>
493
+ <div class="form-row">
494
+ <label for="node-input-namescene" style="width:100px;"><i class="fa fa-play-circle-o"></i> Recall</label>
495
+
496
+ <label for="node-input-GAscene" style="width:20px;">GA</label>
497
+ <input type="text" id="node-input-GAscene" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
498
+
499
+ <label for="node-input-dptscene" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
500
+ <select id="node-input-dptscene" style="width:140px;"></select>
501
+
502
+ <label for="node-input-namescene" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
503
+ <input type="text" id="node-input-namescene" style="width:200px;margin-left: 5px; text-align: left;">
504
+ </div>
505
+ <div class="form-row" id="divValScene" hidden>
506
+ <label for="node-input-valscene" style="width:100px;"></label>
507
+ <label for="node-input-valscene" style="width:20px;">#</label>
508
+ <select id="node-input-valscene" style="width:180px;margin-left: 5px; text-align: left;"></select>
509
+ </div>
510
+ <div class="form-row">
511
+ <label for="node-input-namesceneStatus" style="width:100px;"><i class="fa fa-play-circle-o"></i> Status</label>
512
+
513
+ <label for="node-input-GAsceneStatus" style="width:20px;">GA</label>
514
+ <input type="text" id="node-input-GAsceneStatus" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
515
+
516
+ <label for="node-input-dptsceneStatus" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
517
+ <select id="node-input-dptsceneStatus" style="width:140px;"></select>
518
+
519
+ <label for="node-input-namesceneStatus" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
520
+ <input type="text" id="node-input-namesceneStatus" style="width:200px;margin-left: 5px; text-align: left;">
521
+ </div>
522
+ <br/>
523
+ <br/>
524
+ </div> <!-- // End Tab 1 -->
525
+
526
+ <div id="tabs-2">
527
+ <br/>
528
+ <div class="form-row">
529
+ <label for="node-input-namesceneMulti" style="width:100px;"><i class="fa fa-play-circle-o"></i> Recall</label>
530
+
531
+ <label for="node-input-GAsceneMulti" style="width:20px;">GA</label>
532
+ <input type="text" id="node-input-GAsceneMulti" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
533
+
534
+ <label for="node-input-dptsceneMulti" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
535
+ <select id="node-input-dptsceneMulti" style="width:140px;"></select>
536
+
537
+ <label for="node-input-namesceneMulti" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
538
+ <input type="text" id="node-input-namesceneMulti" style="width:200px;margin-left: 5px; text-align: left;">
539
+ </div>
540
+
541
+ <div>
542
+ <dt><i class="fa fa-code-fork"></i> Scene selector</dt>
543
+ <div class="form-row node-input-rule-container-row">
544
+ <ol id="node-input-rule-container"></ol>
545
+ </div>
546
+ <div class="form-row">
547
+ <p></p>
548
+ </div>
549
+ </div>
550
+ <br/>
551
+ </div> <!-- // End Tab 2 -->
552
+
553
+ <div id="tabs-3">
554
+ <br/>
555
+ <div class="form-row">
556
+ <label for="node-input-enableNodePINS" style="width:240px;">
557
+ <i class="fa fa-circle"></i> Node Input/Output PINs
558
+ </label>
559
+ <select id="node-input-enableNodePINS">
560
+ <option value="no">Hide</option>
561
+ <option value="yes">Show node input/output PINs</option>
562
+ </select>
563
+ </div>
564
+ </div> <!-- // End Tab 3 -->
565
+
566
+ </div> <!-- // End TABS -->
286
567
 
287
568
  </script>
288
569
 
289
570
  <script type="text/markdown" data-help-name="knxUltimateHueScene">
290
571
  This node lets you recall a HUE scene, via KNX.
291
572
 
292
- 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.
573
+ The node has 2 operating options: Simple mode and Multi mode.
574
+ **Simple mode** is the simplest mode, where you can activate one HUE scene using either a boolean Datapoint or a KNX scene Datapoint.
575
+ **Multi mode** is the more powerful one, where for multiple KNX scene numbers, you can activate multiple HUE scenes.
576
+ 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.
577
+ This works also with the HUE scene text field.
293
578
 
294
579
  **General**
295
580
  |Property|Description|
296
581
  |--|--|
297
582
  | KNX GW | Select the KNX gateway to be used |
298
583
  | HUE Bridge | Select the HUE Bridge to be used |
584
+
585
+
586
+ **Single mode tab**
587
+ |Property|Description|
588
+ |--|--|
299
589
  | Hue Scene | HUE scene to control. The avaiable scenes start showing up while you're typing.|
590
+ | Recall as | This sets the calling mode. |
300
591
 
301
592
  **KNX**
302
-
303
593
  |Property|Description|
304
594
  |--|--|
305
595
  | Recall | Choose your group address to be used for recalling the HUE scene. In case of Datapoint 1.x, send *true* to that group address to recall the scene, *false* to switch off all lights belonging to the scene. |
306
596
  | # | Select the KNX scene number. Visible only with datapoint 18.001. |
597
+ | Status | It's the scene status. *True* when the scene is active, *false* when the scene is not active. |
598
+
599
+
600
+ **Multi mode tab**
601
+ |Property|Description|
602
+ |--|--|
603
+ | Recall | Choose your group address and datapoint to be used for recalling the HUE scene. |
604
+
605
+ **Scene selector**
606
+
607
+ The scene selector list, contains all scenes, ativated for each KNX Scene number.
608
+ Click the <code>+add</code> button at the bottom of the list, to add a row.
609
+ Click the <code>X</code> on the far right of each row, to delete the row.
610
+ Drag the <code>toast</code> (the icon with three lines) at the far left of a row, to reorder the item in the list.
307
611
 
308
- **BEHAVIOUR**
309
612
 
613
+ **Behaviour tab**
310
614
  |Property|Description|
311
615
  |--|--|
312
616
  | Node Input/Output PINs | Hide or show the input/output PINs. Input/output PINS allow the node to accept msg input from the flow and send msg output to the flow. Input msg must follow the HUE API v.2 Standards. Please refer to the [official HUE Api page](https://developers.meethue.com/develop/hue-api-v2/api-reference/#resource_light__id__put) |
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
1
2
  const dptlib = require('./../KNXEngine/src/dptlib');
2
3
 
3
4
  module.exports = function (RED) {
@@ -26,6 +27,14 @@ module.exports = function (RED) {
26
27
  node.formatdecimalsvalue = 2;
27
28
  node.hueDevice = config.hueDevice;
28
29
  node.initializingAtStart = false;
30
+ node.sysLogger = require('./utils/sysLogger.js').get({ loglevel: node.server.loglevel || 'error' }); // 08/04/2021 new logger to adhere to the loglevel selected in the config-window
31
+
32
+ // Multi scene
33
+ config.GAsceneMulti = config.GAsceneMulti === undefined ? '' : config.GAsceneMulti;
34
+ config.namesceneMulti = config.namesceneMulti === undefined ? '' : config.namesceneMulti;
35
+ config.dptsceneMulti = config.dptsceneMulti === undefined ? '' : config.dptsceneMulti;
36
+ config.rules = config.rules === undefined ? [] : config.rules;
37
+ config.selectedModeTabNumber = config.selectedModeTabNumber === undefined ? 0 : Number(config.selectedModeTabNumber); // Transform as number
29
38
 
30
39
  // Used to call the status update from the config node.
31
40
  node.setNodeStatus = ({ fill, shape, text, payload }) => {
@@ -42,45 +51,102 @@ module.exports = function (RED) {
42
51
  // This function is called by the knx-hue config node
43
52
  node.handleSend = msg => {
44
53
  let state = {};
45
- try {
46
- switch (msg.knx.destination) {
47
- case config.GAscene:
48
- msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptscene));
49
- if (config.dptscene.startsWith("1.")) {
50
- if (msg.payload === true) {
51
- state = { recall: { action: config.hueSceneRecallType } };
52
- node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene');
54
+ if (config.selectedModeTabNumber === 0) {
55
+ // Sigle
56
+ try {
57
+ switch (msg.knx.destination) {
58
+ case config.GAscene:
59
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptscene));
60
+ if (config.dptscene.startsWith("1.")) {
61
+ if (msg.payload === true) {
62
+ state = { recall: { action: config.hueSceneRecallType } };
63
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene');
64
+ } else {
65
+ // Turn off all light belonging to the scene
66
+ (async () => {
67
+ try {
68
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'stopScene');
69
+ } catch (error) {
70
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info('KNXUltimateHUEConfig: classHUE: handleQueue: stopScene: ' + error.message);
71
+ }
72
+ })();
73
+ }
53
74
  } else {
54
- // Turn off all light belonging to the scene
55
- (async () => {
56
- try {
57
- node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'stopScene');
58
- } catch (error) {
59
- RED.log.error('KNXUltimateHUEConfig: classHUE: handleQueue: stopScene: ' + error.message);
60
- }
61
- })();
62
- }
63
- } else {
64
- if (Number(config.valscene) === msg.payload.scenenumber && msg.payload.save_recall === 0) {
65
- state = { recall: { action: config.hueSceneRecallType } };
66
- node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene');
75
+ if (Number(config.valscene) === msg.payload.scenenumber && msg.payload.save_recall === 0) {
76
+ state = { recall: { action: config.hueSceneRecallType } };
77
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene');
78
+ }
67
79
  }
80
+ node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload });
81
+ break;
82
+ default:
83
+ break;
84
+ }
85
+ } catch (error) {
86
+ node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE single: ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
87
+ }
88
+ } else if (config.selectedModeTabNumber === 1) {
89
+ // Multi
90
+ try {
91
+ if (config.GAsceneMulti !== undefined && config.dptsceneMulti !== undefined && config.rules.length !== 0) {
92
+ if (config.GAsceneMulti === msg.knx.destination) {
93
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptsceneMulti));
94
+ // row is: { rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID:rowRuleHUESceneID, rowRuleRecallAs:rowRuleRecallAs}
95
+ config.rules.forEach(row => {
96
+ if (row.rowRuleKNXSceneNumber !== undefined
97
+ && row.rowRuleHUESceneID !== undefined
98
+ && row.rowRuleRecallAs !== undefined
99
+ && Number(row.rowRuleKNXSceneNumber) === Number(msg.payload.scenenumber)
100
+ && Number(msg.payload.save_recall) === 0
101
+ && node.serverHue.hueManager !== undefined) {
102
+ state = { recall: { action: row.rowRuleRecallAs } };
103
+ node.serverHue.hueManager.writeHueQueueAdd(row.rowRuleHUESceneID, state, 'setScene');
104
+ node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload });
105
+ }
106
+ });
68
107
  }
69
- node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload });
70
- break;
71
- default:
72
- break;
108
+ }
109
+ } catch (error) {
110
+ node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE multi: ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
73
111
  }
74
- } catch (error) {
75
- node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
76
112
  }
77
113
  };
78
114
 
79
115
  node.handleSendHUE = (_event) => {
80
116
  try {
81
- if (_event.id === config.hueDevice) {
82
- // Output the msg to the flow
83
- node.send(_event);
117
+ if (Number(config.selectedModeTabNumber) === 0 && config.hueDevice !== undefined && _event.id === config.hueDevice) {
118
+ // Single mode
119
+ const knxMsgPayload = {};
120
+ knxMsgPayload.topic = config.GAsceneStatus;
121
+ knxMsgPayload.dpt = config.dptsceneStatus;
122
+ if (_event.hasOwnProperty("status") && _event.status.hasOwnProperty("active")) {
123
+ knxMsgPayload.payload = _event.status.active !== "inactive";
124
+ // Send to KNX bus
125
+ if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined && node.server !== undefined) {
126
+ node.server.writeQueueAdd({
127
+ grpaddr: knxMsgPayload.topic,
128
+ payload: knxMsgPayload.payload,
129
+ dpt: knxMsgPayload.dpt,
130
+ outputtype: "write",
131
+ nodecallerid: node.id,
132
+ });
133
+ }
134
+ node.status({
135
+ fill: "blue",
136
+ shape: "dot",
137
+ text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${new Date().getDate()}, ${new Date().toLocaleTimeString()})`,
138
+ });
139
+ // Output the msg to the flow
140
+ node.send(_event);
141
+ }
142
+ } else if (Number(config.selectedModeTabNumber) === 1 && config.rules !== undefined) {
143
+ // Multi mode
144
+ config.rules.forEach(row => {
145
+ if (row.rowRuleHUESceneID !== undefined && _event.id === row.rowRuleHUESceneID) {
146
+ // Output the msg to the flow
147
+ node.send(_event);
148
+ }
149
+ });
84
150
  }
85
151
  } catch (error) {
86
152
  node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
@@ -166,7 +166,7 @@
166
166
  <br />
167
167
  <br />
168
168
  <p align="center">
169
- <i class="fa-solid fa-circle-notch fa-spin fa-8x"></i>
169
+ <i class="fa-solid fa-circle-notch fa-spin fa-4x"></i>
170
170
  </p>
171
171
  <br />
172
172
  <label for="node-input-server" >
@@ -165,7 +165,7 @@
165
165
  <br />
166
166
  <br />
167
167
  <p align="center">
168
- <i class="fa-solid fa-temperature-full fa-beat fa-8x"></i>
168
+ <i class="fa-solid fa-temperature-full fa-beat fa-4x"></i>
169
169
  </p>
170
170
  <br />
171
171
  <label for="node-input-server" >
package/package.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "2.2.29",
7
- "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 control.",
6
+ "version": "2.2.31",
7
+ "description": "Control your KNX intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.",
8
8
  "dependencies": {
9
9
  "binary-parser": "2.2.1",
10
10
  "crypto-js": "4.2.0",
@@ -54,7 +54,7 @@
54
54
  "eib",
55
55
  "konnex",
56
56
  "IOT",
57
- "hue"
57
+ "philips hue"
58
58
  ],
59
59
  "author": "Supergiovane",
60
60
  "license": "MIT",