node-red-contrib-knx-ultimate 2.4.28 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,13 +6,33 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
- **Version 2.4.28**
10
- - temporary reveft to 2.4.23
9
+ **Version 2.5.1** - Mai 2024<br/>
10
+ - Warning: Node-Red version **equals or major than 3.1.1** is needed to run this node.<br/>
11
+ - NEW: HUE light node: you can now override the day/night mode by setting the day mode temporary, whenever you fast toggles the light switch on then off within 10 seconds. There are multiple choiches to select from. <br/>
12
+
13
+ **Version 2.5.0** - Mai 2024<br/>
14
+ - Warning: Node-Red version **equals or major than 3.1.1** is needed to run this node.<br/>
15
+ - HUE light node: internally calculate the average color xy and temp in kelvin, for group_lights (not emitted by the HUE Bridge).<br/>
16
+ - KNOW ISSUE: HUE light node: grouped_lights: the status group address is updated multiple times, equals to the lights contained in the grouped_light. To avoid repeating the same telegram multiple times, simply enable the RBE filter on the KNX node. <br/>
17
+
18
+ **Version 2.4.27** - Mai 2024<br/>
19
+ - Warning: Node-Red version **equals or major than 3.1.1** is needed to run this node.<br/>
20
+ - HUE light node: fixed status refresh of kelvin and brightness of a light belonging to a grouped_light.<br/>
21
+ - KNOW ISSUE: HUE light node: grouped_lights: the status group address is updated multiple times, equals to the lights contained in the grouped_light. To avoid repeating the same telegram multiple times, simply enable the RBE filter on the KNX node. <br/>
22
+
23
+ **Version 2.4.25** - Mai 2024<br/>
24
+ - Warning: Node-Red version **equals or major than 3.1.1** is needed to run this node.<br/>
25
+ - HUE light node: fixed status sent to the KNX bus after issuing a read request for grouped_lights.<br/>
26
+ - KNOW ISSUE: HUE light node: grouped_lights: the status group address is updated multiple times, equals to the lights contained in the grouped_light. To avoid repeating the same telegram multiple times, simply enable the RBE filter on the KNX node. <br/>
27
+
28
+ **Version 2.4.24** - Mai 2024<br/>
29
+ - Warning: Node-Red version **equals or major than 3.1.1** is needed to run this node.<br/>
30
+ - HUE light node: fixed kelvin temp status with datapoint 7.600, that sent wrong values to the bus.<br/>
11
31
 
12
32
  **Version 2.4.23** - Mai 2024<br/>
13
33
  - Warning: Node-Red version **equals or major than 3.1.1** is needed to run this node.<br/>
14
34
  - HUE Scene node: fixed max scene count to 64.<br/>
15
-
35
+
16
36
  **Version 2.4.22** - April 2024<br/>
17
37
  - Warning: this version uses the Node-Red plugin system; the Node-Red version must be **equals or major than 3.1.1**<br/>
18
38
  - HUE button node: NEW: now you can select the value and the dim direction to be transmitted, when Toggle Status is set to unchecked. Thanks @cybersmart-eu for the suggestion.<br/>
package/README.md CHANGED
@@ -50,14 +50,17 @@ msg.payload = {red:255, green:200, blue:30} // Put some colors in our life
50
50
 
51
51
  * See <a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/blob/master/CHANGELOG.md">here the changelog</a>
52
52
 
53
+
54
+ ## SUPPORTED TECHNOLOGIES
55
+
53
56
  |Technology|Supported|
54
57
  |--|--|
55
58
  | KNX Tunnelling | ![](https://placehold.co/200x20/green/white?text=YES) |
56
59
  | KNX Routing | ![](https://placehold.co/200x20/green/white?text=YES) |
57
60
  | KNX Secure Tunnelling | ![](https://placehold.co/200x20/orange/white?text=UNDER+DEVELOPMENT) |
58
61
  | KNX Secure Routing | ![](https://placehold.co/200x20/red/white?text=NO) |
59
- | KNX 3rd PARTY IOT API | ![](https://placehold.co/200x20/orange/white?text=UNDER+DEVELOPMENT) |
60
-
62
+ | KNX 3rd PARTY IOT API client | ![](https://placehold.co/200x20/orange/white?text=UNDER+DEVELOPMENT) |
63
+ | Matter | ![](https://placehold.co/200x20/blue/white?text=UNDER+BRAINSTORMING) |
61
64
 
62
65
  <br/>
63
66
 
@@ -9,6 +9,7 @@ const HueClass = require("./utils/hueEngine").classHUE;
9
9
  const loggerEngine = require("./utils/sysLogger");
10
10
  const hueColorConverter = require("./utils/colorManipulators/hueColorConverter");
11
11
 
12
+
12
13
  module.exports = (RED) => {
13
14
  function hueConfig(config) {
14
15
  RED.nodes.createNode(this, config);
@@ -50,6 +51,9 @@ module.exports = (RED) => {
50
51
  node.nodeClients.forEach((_oClient) => {
51
52
  const oClient = _oClient;
52
53
  try {
54
+ // if (_event.type === "light" || _event.type === "grouped_light") {
55
+ // console.log(_event);
56
+ // }
53
57
  if (oClient.handleSendHUE !== undefined) oClient.handleSendHUE(_event);
54
58
  } catch (error) {
55
59
  if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`Errore node.hueManager.on(event): ${error.message}`);
@@ -65,7 +69,9 @@ module.exports = (RED) => {
65
69
  node.timerDoInitialRead = setTimeout(() => {
66
70
  (async () => {
67
71
  try {
72
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`HTTP getting resource from HUE bridge : ${node.name}`);
68
73
  await node.loadResourcesFromHUEBridge();
74
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Total HUE resources count : ${node.hueAllResources.length}`);
69
75
  } catch (error) {
70
76
  node.linkStatus = "disconnected";
71
77
  node.nodeClients.forEach((_oClient) => {
@@ -114,6 +120,7 @@ module.exports = (RED) => {
114
120
  };
115
121
  node.startWatchdogTimer();
116
122
 
123
+ // Functions called from the nodes ----------------------------------------------------------------
117
124
  // Query the HUE Bridge to return the resources
118
125
  node.loadResourcesFromHUEBridge = async () => {
119
126
  if (node.linkStatus === "disconnected") return;
@@ -173,11 +180,22 @@ module.exports = (RED) => {
173
180
  };
174
181
 
175
182
  // Return an array of light belonging to the groupID
176
- node.getAllLightsBelongingToTheGroup = async function getAllLightsBelongingToTheGroup(_groupID) {
183
+ node.getAllLightsBelongingToTheGroup = async function getAllLightsBelongingToTheGroup(_groupID, refreshResourcesFromBridge = true) {
177
184
  if (node.hueAllResources === undefined || node.hueAllResources === null) return;
178
185
  const retArr = [];
186
+ let filteredResource;
179
187
  try {
180
- await node.loadResourcesFromHUEBridge();
188
+ if (refreshResourcesFromBridge === true) {
189
+ await node.loadResourcesFromHUEBridge();
190
+ }
191
+ // filteredResource = node.hueAllResources.filter((a) => a.id === _groupID);
192
+ // if (filteredResource[0].type === "grouped_light") {
193
+ // filteredResource = node.hueAllResources.filter((a) => a.services);
194
+ // filteredResource = filteredResource.filter((a) => a.services).filter((b) => b.type === "light");
195
+ // if (filteredResource.length > 0) {
196
+ // console.log(filteredResource)
197
+ // }
198
+ // }
181
199
  node.hueAllResources.forEach((res) => {
182
200
  if (res.services !== undefined && res.services.length > 0) {
183
201
  res.services.forEach((serv) => {
@@ -187,7 +205,8 @@ module.exports = (RED) => {
187
205
  for (let index = 0; index < children.length; index++) {
188
206
  const element = children[index];
189
207
  const oLight = node.hueAllResources.filter((a) => a.id === element.rid);
190
- if (oLight !== null && oLight !== undefined) retArr.push({ groupID: _groupID, light: oLight });
208
+ //if (oLight !== null && oLight !== undefined) retArr.push({ groupID: _groupID, light: oLight[0] });
209
+ if (oLight !== null && oLight !== undefined) retArr.push(oLight[0]);
191
210
  }
192
211
  }
193
212
  }
@@ -363,6 +382,46 @@ module.exports = (RED) => {
363
382
  }
364
383
  };
365
384
 
385
+ /**
386
+ * Get average color XY from a light array
387
+ * @param {array} _arrayLights - Light array
388
+ * @returns { x,y,mirek,brightness } - Object containing all infos
389
+ */
390
+ node.getAverageColorsXYBrightnessAndTemperature = async function getAverageColorsXYBrightnessAndTemperature(_arrayLights) {
391
+ let x; let y; let mirek; let brightness;
392
+ let countColor = 0, countColor_Temperature = 0, countDimming = 0;
393
+ _arrayLights.forEach((element) => {
394
+ if (element.color !== undefined && element.color.xy !== undefined) {
395
+ if (x === undefined) { x = 0; y = 0; }
396
+ x += element.color.xy.x;
397
+ y += element.color.xy.y;
398
+ countColor += 1;
399
+ }
400
+ if (element.color_temperature !== undefined && element.color_temperature.mirek !== undefined) {
401
+ if (mirek === undefined) mirek = 0;
402
+ mirek += element.color_temperature.mirek;
403
+ countColor_Temperature += 1;
404
+ }
405
+ if (element.dimming !== undefined && element.dimming.brightness !== undefined) {
406
+ if (brightness === undefined) brightness = 0;
407
+ brightness += element.dimming.brightness;
408
+ countDimming += 1;
409
+ }
410
+ });
411
+ // Calculate and return the averages
412
+ const retX = countColor === 0 ? undefined : x / countColor;
413
+ const retY = countColor === 0 ? undefined : y / countColor;
414
+ const retMirek = countColor_Temperature === 0 ? undefined : mirek / countColor_Temperature;
415
+ const retBrightness = countDimming === 0 ? undefined : brightness / countDimming;
416
+
417
+ return {
418
+ x: retX, y: retY, mirek: retMirek, brightness: retBrightness
419
+ };
420
+ };
421
+ // END functions called from the nodes ----------------------------------------------------------------
422
+
423
+
424
+
366
425
  node.addClient = (_Node) => {
367
426
  // Update the node hue device, as soon as a node register itself to hue-config nodeClients
368
427
  if (node.nodeClients.filter((x) => x.id === _Node.id).length === 0) {
@@ -129,6 +129,8 @@
129
129
 
130
130
  hueDevice: { value: "" },
131
131
  hueDeviceObject: { value: {} },
132
+
133
+ restoreDayMode: { value: "no" }
132
134
  },
133
135
  inputs: 0,
134
136
  outputs: 0,
@@ -1406,11 +1408,21 @@
1406
1408
  </div>
1407
1409
  <div class="form-row">
1408
1410
  <label style="width:170px" for="node-input-invertDayNight">
1409
- <i class="fa fa-shuffle"></i> Invert day/night values
1411
+ <i class="fa fa-shuffle"></i> Invert day/night value
1410
1412
  </label>
1411
1413
  <input type="checkbox" id="node-input-invertDayNight"
1412
1414
  style="display:inline-block; width:auto; vertical-align:top;" />
1413
1415
  </div>
1416
+ <div class="form-row">
1417
+ <label for="node-input-restoreDayMode" style="width:260px;">
1418
+ <i class="fa fa-circle"></i> Override night mode
1419
+ </label>
1420
+ <select id="node-input-restoreDayMode">
1421
+ <option value="no">No</option>
1422
+ <option value="setDayByFastSwitchLightSingle">Switch to DAY mode by rapid switching the ligth off then on. (This light only)</option>
1423
+ <option value="setDayByFastSwitchLightALL">Switch to DAY mode by rapid switching the ligth off then on (apply yo ALL light nodes)</option>
1424
+ </select>
1425
+ </div>
1414
1426
  </div>
1415
1427
  </div>
1416
1428
  <br/>
@@ -1521,8 +1533,9 @@ For controlling the "V" (brightness) of the HSV, please use the standard control
1521
1533
  | Switch on behaviour | It sets the behaviour of your lights when switched on. You can choose from differents behaviours.<br/>**Select color:** the light will be switched on with the color of your choice. To change color, just CLICK on the color selector (under the *Select color* control).<br/>**Select temperature and brightness:** the light will be switched on with the temperature (Kelvin) and brightness (0-100) of your choice.<br/>**None:** the light will retain its last status. In case you've enable the night lighting, after the night time ends, the lamp will resume the color/temperature/brightness state set at day time. |
1522
1534
  | Night Lighting | It allows to set a particular light color/brightness at nighttime. The options are the same as the daytime. You could select either a temperature/brightness or color. A cozy temperature of 2700 Kelvin, with a brightness of 10% or 20%, is a good choice for bathroom's night light.|
1523
1535
  | Day/Night | Select the group address used to set the day/night behaviour. The group address value is _true_ if daytime, _false_ if nighttime. |
1524
- | Invert day/night values | Invert the values of _Day/Night_ group address. Default value is **unchecked**. |
1536
+ | Invert day/night value | Invert the values of _Day/Night_ group address. Default value is **unchecked**. |
1525
1537
  | Read status at startup | Read the status at startup and emit the event to the KNX bus at startup/reconnection. (Default "no")|
1538
+ | Override night mode | You can override the night mode by manually switching the light as described here: **Switch to DAY mode by rapid switching the ligth off then on (This light only)** does what described and acts only on this light. **Switch to DAY mode by rapid switching the ligth off then on (apply yo ALL light nodes)** acts to ALL Light nodes, by setting the Day/Night group address to Day mode. |
1526
1539
  | 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. This is an example msg, that turns on the light: <code>msg.on = {"on":true}</code>. Please refer to the [official HUE Api page](https://developers.meethue.com/develop/hue-api-v2/api-reference/#resource_light__id__put) |
1527
1540
 
1528
1541
  ### Note
@@ -62,7 +62,10 @@ module.exports = function (RED) {
62
62
  config.colorAtSwitchOnNightTime = config.colorAtSwitchOnNightTime.replace("geen", "green");
63
63
  config.dimSpeed = (config.dimSpeed === undefined || config.dimSpeed === '') ? 5000 : Number(config.dimSpeed);
64
64
  config.HSVDimSpeed = (config.HSVDimSpeed === undefined || config.HSVDimSpeed === '') ? 5000 : Number(config.HSVDimSpeed);
65
- config.invertDimTunableWhiteDirection = config.invertDimTunableWhiteDirection === undefined ? false : true;
65
+ config.invertDimTunableWhiteDirection = config.invertDimTunableWhiteDirection !== undefined;
66
+ config.restoreDayMode = config.restoreDayMode === undefined ? "no" : config.restoreDayMode; // no or setDayByFastSwitchLightSingle or setDayByFastSwitchLightALL
67
+ node.timerCheckForFastLightSwitch = null;
68
+ config.invertDayNight = config.invertDayNight === undefined ? false : config.invertDayNight;
66
69
  node.HSVObject = null; //{ h, s, v };// Store the current light calculated HSV
67
70
 
68
71
  // Transform HEX in RGB and stringified json in json oblects.
@@ -96,7 +99,6 @@ module.exports = function (RED) {
96
99
  RED.log.error(`knxUltimateHueLight: config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnNightTime): ${error.message} : ${error.stack || ""} `);
97
100
  config.colorAtSwitchOnNightTime = "";
98
101
  }
99
-
100
102
  }
101
103
 
102
104
  // Used to call the status update from the config node.
@@ -134,6 +136,42 @@ module.exports = function (RED) {
134
136
  switch (msg.knx.destination) {
135
137
  case config.GALightSwitch:
136
138
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch));
139
+
140
+ // 15/05/2024 Supergiovane: check the Override to Day option
141
+ // config.restoreDayMode can be: no or setDayByFastSwitchLightSingle or setDayByFastSwitchLightALL
142
+ // ----------------------------------------------------------
143
+ if (config.restoreDayMode === "setDayByFastSwitchLightSingle" || config.restoreDayMode === "setDayByFastSwitchLightALL") {
144
+ if (node.DayTime === false) {
145
+ if (msg.payload === true) {
146
+ if (node.timerCheckForFastLightSwitch === null) {
147
+ node.timerCheckForFastLightSwitch = setTimeout(() => {
148
+ node.DayTime = false;
149
+ RED.log.debug("knxUltimateHueLight: node.timerCheckForFastLightSwitch: set daytime to false after node.timerCheckForFastLightSwitch elapsed");
150
+ node.timerCheckForFastLightSwitch = null;
151
+ }, 10000); // 10 seconds
152
+ } else {
153
+ if (config.restoreDayMode === "setDayByFastSwitchLightALL") {
154
+ // Turn off the Day/Night group address
155
+ if (config.GADaylightSensor !== undefined && config.GADaylightSensor !== "") {
156
+ if (node.timerCheckForFastLightSwitch !== null) { clearTimeout(node.timerCheckForFastLightSwitch); node.timerCheckForFastLightSwitch = null; }
157
+ RED.log.debug(`knxUltimateHueLight: node.timerCheckForFastLightSwitch: set daytime the group address ${config.GADaylightSensor}`);
158
+ node.server.writeQueueAdd({
159
+ grpaddr: config.GADaylightSensor,
160
+ payload: config.invertDayNight === false,
161
+ dpt: config.dptDaylightSensor,
162
+ outputtype: "write",
163
+ nodecallerid: node.id,
164
+ });
165
+ }
166
+ }
167
+ node.DayTime = true;
168
+ RED.log.debug("knxUltimateHueLight: node.timerCheckForFastLightSwitch: set daytime to true");
169
+ }
170
+ }
171
+ }
172
+ }
173
+ // ----------------------------------------------------------
174
+
137
175
  if (msg.payload === true) {
138
176
  // From HUE Api core concepts:
139
177
  // If you try and control multiple conflicting parameters at once e.g. {"color": {"xy": {"x":0.5,"y":0.5}}, "color_temperature": {"mirek": 250}}
@@ -309,7 +347,7 @@ module.exports = function (RED) {
309
347
  break;
310
348
  case config.GADaylightSensor:
311
349
  node.DayTime = Boolean(dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptDaylightSensor)));
312
- if (config.invertDayNight !== undefined && config.invertDayNight === true) node.DayTime = !node.DayTime;
350
+ if (config.invertDayNight === true) node.DayTime = !node.DayTime;
313
351
  if (config.specifySwitchOnBrightness === "no") {
314
352
  // This retains the HUE device status while daytime, to be restored after nighttime elapsed. https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/298
315
353
  if (node.DayTime === false) {
@@ -317,7 +355,7 @@ module.exports = function (RED) {
317
355
  if (node.isGrouped_light === true) {
318
356
  (async () => {
319
357
  try {
320
- const retLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice);
358
+ const retLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
321
359
  node.HUELightsBelongingToGroupWhileDaytime = cloneDeep(retLights); // DayTime has switched to false: save the lights belonging to the group into the HUELightsBelongingToGroupWhileDaytime array
322
360
  } catch (error) { /* empty */ }
323
361
  })();
@@ -509,16 +547,56 @@ module.exports = function (RED) {
509
547
  if (ret !== undefined) node.updateKNXLightColorState(node.currentHUEDevice.color, "response");
510
548
  break;
511
549
  case config.GALightKelvinPercentageState:
550
+ // The kelvin level belongs to the group defice, so i don't need to get the first light in the collection (if the device is a grouped_light)
512
551
  ret = node.currentHUEDevice.color_temperature.mirek;
513
552
  if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
553
+ // if (node.isGrouped_light === false) {
554
+ // ret = node.currentHUEDevice.color_temperature.mirek;
555
+ // if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
556
+ // } else {
557
+ // (async () => {
558
+ // try {
559
+ // // Find the first light in the collection, having the color_temperature capabilities
560
+ // const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
561
+ // for (let index = 0; index < devices.length; index++) {
562
+ // const element = devices[index];
563
+ // if (element.light[0].color_temperature !== undefined) {
564
+ // ret = element.light[0].color_temperature.mirek;
565
+ // break;
566
+ // }
567
+ // }
568
+ // if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
569
+ // } catch (error) { /* empty */ }
570
+ // })();
571
+ // }
514
572
  break;
515
573
  case config.GALightBrightnessState:
516
574
  ret = node.currentHUEDevice.dimming.brightness;
517
575
  if (ret !== undefined) node.updateKNXBrightnessState(ret, "response");
518
576
  break;
519
577
  case config.GALightKelvinState:
578
+ // The kelvin level belongs to the group defice, so i don't need to get the first light in the collection (if the device is a grouped_light)
520
579
  ret = node.currentHUEDevice.color_temperature.mirek;
521
580
  if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
581
+ // if (node.isGrouped_light === false) {
582
+ // ret = node.currentHUEDevice.color_temperature.mirek;
583
+ // if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
584
+ // } else {
585
+ // (async () => {
586
+ // try {
587
+ // // Find the first light in the collection, having the color_temperature capabilities
588
+ // const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
589
+ // for (let index = 0; index < devices.length; index++) {
590
+ // const element = devices[index];
591
+ // if (element.light[0].color_temperature !== undefined) {
592
+ // ret = element.light[0].color_temperature.mirek;
593
+ // break;
594
+ // }
595
+ // }
596
+ // if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
597
+ // } catch (error) { /* empty */ }
598
+ // })();
599
+ // }
522
600
  break;
523
601
  default:
524
602
  break;
@@ -870,22 +948,93 @@ module.exports = function (RED) {
870
948
  };
871
949
  // ***********************************************************
872
950
 
873
- node.handleSendHUE = (_event) => {
951
+ node.handleSendHUE = async (_event) => {
952
+ if (_event === undefined) return;
953
+ if (_event.type !== 'grouped_light' && _event.type !== 'light') return;
954
+
955
+ // !!!! >>> if the node is a grouped_light, the only values required, thus present, by HUE apis are "on" and "dimming".
956
+ // !!!! >>> For all others properties like for example color and tunable white, i must get the data from one of the child lights.
957
+
958
+ //(async () => {
959
+ // Check and set canContinue true or false ------------------------------------------------------------
874
960
  try {
875
- const deviceByRef = cloneDeep(_event);
876
- if (deviceByRef.id === node.hueDevice) {
877
- if (node.currentHUEDevice === undefined || node.serverHue === null || node.serverHue === undefined) {
878
- node.setNodeStatusHue({
879
- fill: "red",
880
- shape: "ring",
881
- text: "Rejected HUE message. I'm connecting to the Bridge...",
882
- payload: "",
883
- });
884
- return;
961
+ if (node.currentHUEDevice === undefined || node.serverHue === null || node.serverHue === undefined) {
962
+ node.setNodeStatusHue({
963
+ fill: "red",
964
+ shape: "ring",
965
+ text: "Rejected HUE light settings. I'm still not ready...",
966
+ payload: "",
967
+ });
968
+ return;
969
+ }
970
+
971
+ const receivedHUEObject = cloneDeep(_event);
972
+
973
+ //#region "CALCULATE COLOR AND COLOR TEMPERATURE OF THE LIGHTS BELONGING TO THE GROUPED_LIGHT"
974
+ // Check wether the incoming _event belongs to a light belonging to the group_light
975
+ if (node.isGrouped_light === true && receivedHUEObject.type === 'light') {
976
+ // Handling of not by HUE handled Color Temperature and ColorXY
977
+ let modifiedLight;
978
+ const groupChilds = [];
979
+ let AverageColorsXYBrightnessAndTemperature; // Average color xy and color temp if the node is a grouped light.
980
+ // If the current node is a grouped lights, i must check wether the receivedHUEObject (containing a light) belongs to the node lights collection.
981
+ // Find all the lights in the collection, having the color_temperature capabilities, belonging to the group.
982
+ const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
983
+ if (devices.length === 0) {
984
+ devices.push(receivedHUEObject);
885
985
  }
886
986
 
987
+ for (let index = 0; index < devices.length; index++) {
988
+ const element = devices[index];
989
+ if (receivedHUEObject.id === element.id) {
990
+ modifiedLight = element;
991
+ // The dimming is not necessary, beacause the HUE API already sends a group_light event with the average brightness //if (receivedHUEObject.dimming !== undefined) modifiedLight.dimming = { brightness: receivedHUEObject.dimming.brightness };
992
+ if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined) modifiedLight.color = receivedHUEObject.color;
993
+ if (receivedHUEObject.color_temperature !== undefined) modifiedLight.color_temperature = receivedHUEObject.color_temperature;
994
+ groupChilds.push(modifiedLight);
995
+ } else {
996
+ // Simply append the light
997
+ if ((element.color_temperature !== undefined && element.color_temperature.mirek !== undefined)
998
+ || (element.color !== undefined && element.color.xy !== undefined)) {
999
+ groupChilds.push(element);
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ // Use the arithmetic average of color xy, brightness and color temperature "averageColorXYandKelvinOfGroupedLights"
1005
+ if (groupChilds !== undefined) AverageColorsXYBrightnessAndTemperature = await node.serverHue.getAverageColorsXYBrightnessAndTemperature(groupChilds);
1006
+
1007
+ // Set the new values based on average calculated above
1008
+ // CHECK FIRST THE COLOR_TEMPERATURE, because it can be undefined, because the current selected color
1009
+ // is out of the mirek range, so it cannot be represented with the colore temperature.
1010
+ if (receivedHUEObject.color_temperature !== undefined && AverageColorsXYBrightnessAndTemperature.mirek !== undefined) {
1011
+ receivedHUEObject.color_temperature.mirek = AverageColorsXYBrightnessAndTemperature.mirek;
1012
+ node.currentHUEDevice.color_temperature.mirek = AverageColorsXYBrightnessAndTemperature.mirek;
1013
+ node.updateKNXLightKelvinPercentageState(receivedHUEObject.color_temperature.mirek);
1014
+ node.updateKNXLightKelvinState(receivedHUEObject.color_temperature.mirek);
1015
+ } else if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined && AverageColorsXYBrightnessAndTemperature.x !== undefined) {
1016
+ receivedHUEObject.color.xy.x = AverageColorsXYBrightnessAndTemperature.x;
1017
+ receivedHUEObject.color.xy.y = AverageColorsXYBrightnessAndTemperature.y;
1018
+ node.currentHUEDevice.color.xy.x = AverageColorsXYBrightnessAndTemperature.x;
1019
+ node.currentHUEDevice.color.xy.y = AverageColorsXYBrightnessAndTemperature.y;
1020
+ node.updateKNXLightColorState(receivedHUEObject.color);
1021
+ }
1022
+ // The dimming is not necessary, beacause the HUE API already sends a group_light event with the average brightness
1023
+ // if (receivedHUEObject.dimming !== undefined && AverageColorsXYBrightnessAndTemperature.brightness !== undefined) {
1024
+ // receivedHUEObject.dimming = { brightness: AverageColorsXYBrightnessAndTemperature.brightness };
1025
+ // }
1026
+ // --------------------------------------------------------------------------------------------------
1027
+ if (modifiedLight !== undefined && modifiedLight.metadata !== undefined && modifiedLight.metadata.name !== undefined) {
1028
+ receivedHUEObject.lightName = modifiedLight.metadata.name;
1029
+ }
1030
+ node.send(receivedHUEObject);
1031
+ return;
1032
+ }
1033
+ //#endregion
1034
+
1035
+ if (receivedHUEObject.id === node.hueDevice) {
887
1036
  // Output the msg to the flow
888
- node.send(deviceByRef);
1037
+ node.send(receivedHUEObject);
889
1038
 
890
1039
  // // DEBUG testing enable/disable HTML UI Tabs
891
1040
  //delete _event.dimming;
@@ -893,53 +1042,39 @@ module.exports = function (RED) {
893
1042
  //delete _event.color_temperature;
894
1043
  //delete _event.color_temperature_delta;
895
1044
 
896
- // As grouped_light doesn't contain all requested properties, i find the first light in the group, and use this below in the code
897
- // If the event type is grouped light, and there are missing properties, i infer these missing properties from the first light in the group!
898
- if ((deviceByRef.color !== undefined || deviceByRef.dimming !== undefined || deviceByRef.color_temperature !== undefined) && deviceByRef.type === 'grouped_light') {
899
- try {
900
- const firstLightInGroup = node.serverHue.getFirstLightInGroup(deviceByRef.id);
901
- if (firstLightInGroup !== null && firstLightInGroup !== undefined) {
902
- if (deviceByRef.color === undefined) {
903
- deviceByRef.color = firstLightInGroup.color;
904
- }
905
- if (deviceByRef.color_temperature === undefined) {
906
- deviceByRef.color_temperature = firstLightInGroup.color_temperature;
907
- }
908
- }
909
- } catch (error) { }
910
- }
911
-
912
- if (deviceByRef.on !== undefined) {
913
- node.updateKNXLightState(deviceByRef.on.on);
1045
+ if (receivedHUEObject.on !== undefined) {
1046
+ node.updateKNXLightState(receivedHUEObject.on.on);
914
1047
  // In case of switch off, set the dim to zero
915
- if (deviceByRef.on.on === false && (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === "onhueoff")) {
1048
+ if (receivedHUEObject.on.on === false && (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === "onhueoff")) {
916
1049
  node.updateKNXBrightnessState(0);
917
- if (deviceByRef.dimming !== undefined) delete deviceByRef.dimming; // Remove event.dimming, because has beem handled by this function and i don't want the function below to take care of it.
918
- } else if (deviceByRef.on.on === true && node.currentHUEDevice.on.on === false) {
919
- // Turn on always update the dimming KNX Status value as well.
1050
+ if (receivedHUEObject.dimming !== undefined) delete receivedHUEObject.dimming; // Remove event.dimming, because has beem handled by this function and i don't want the function below to take care of it.
1051
+ } else if (receivedHUEObject.on.on === true && node.currentHUEDevice.on.on === false) {
920
1052
  let brightVal = 50;
921
- if (node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness !== undefined) brightVal = node.currentHUEDevice.dimming.brightness;
1053
+ // Turn on always update the dimming KNX Status value as well.
1054
+ if (node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness !== undefined) {
1055
+ brightVal = node.currentHUEDevice.dimming.brightness;
1056
+ }
922
1057
  node.updateKNXBrightnessState(brightVal);
923
1058
  }
924
- node.currentHUEDevice.on.on = deviceByRef.on.on;
1059
+ node.currentHUEDevice.on.on = receivedHUEObject.on.on;
925
1060
  }
926
1061
 
927
- if (deviceByRef.color !== undefined) { // fixed https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/287
928
- node.updateKNXLightColorState(deviceByRef.color);
929
- node.currentHUEDevice.color = deviceByRef.color;
1062
+ if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined) { // fixed https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/287
1063
+ node.updateKNXLightColorState(receivedHUEObject.color);
1064
+ node.currentHUEDevice.color = receivedHUEObject.color;
930
1065
  }
931
1066
 
932
- if (deviceByRef.dimming !== undefined && deviceByRef.dimming.brightness !== undefined) {
1067
+ if (receivedHUEObject.dimming !== undefined && receivedHUEObject.dimming.brightness !== undefined) {
933
1068
  // Once upon n a time, the light transmit the brightness value of 0.39.
934
1069
  // To avoid wrongly turn light state on, exit
935
- if (deviceByRef.dimming.brightness < 1) deviceByRef.dimming.brightness = 0;
936
- if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && deviceByRef.dimming.brightness === 0) {
1070
+ if (receivedHUEObject.dimming.brightness < 1) receivedHUEObject.dimming.brightness = 0;
1071
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && receivedHUEObject.dimming.brightness === 0) {
937
1072
  // Do nothing, because the light is off and the dimming also is 0
938
1073
  } else {
939
- if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && (deviceByRef.on === undefined || (deviceByRef.on !== undefined && deviceByRef.on.on === true))) node.updateKNXLightState(deviceByRef.dimming.brightness > 0);
940
- node.updateKNXBrightnessState(deviceByRef.dimming.brightness);
1074
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && (receivedHUEObject.on === undefined || (receivedHUEObject.on !== undefined && receivedHUEObject.on.on === true))) node.updateKNXLightState(receivedHUEObject.dimming.brightness > 0);
1075
+ node.updateKNXBrightnessState(receivedHUEObject.dimming.brightness);
941
1076
  // If the brightness reaches zero, the hue lamp "on" property must be set to zero as well
942
- if (deviceByRef.dimming.brightness === 0 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true) {
1077
+ if (receivedHUEObject.dimming.brightness === 0 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true) {
943
1078
  node.serverHue.hueManager.writeHueQueueAdd(
944
1079
  node.hueDevice,
945
1080
  { on: { on: false } },
@@ -947,13 +1082,14 @@ module.exports = function (RED) {
947
1082
  );
948
1083
  node.currentHUEDevice.on.on = false;
949
1084
  }
950
- node.currentHUEDevice.dimming.brightness = deviceByRef.dimming.brightness;
1085
+ node.currentHUEDevice.dimming.brightness = receivedHUEObject.dimming.brightness;
951
1086
  }
952
1087
  }
953
- if (deviceByRef.color_temperature !== undefined && deviceByRef.color_temperature.mirek !== undefined) {
954
- node.updateKNXLightKelvinPercentageState(deviceByRef.color_temperature.mirek);
955
- node.updateKNXLightKelvinState(deviceByRef.color_temperature.mirek);
956
- node.currentHUEDevice.color_temperature.mirek = deviceByRef.color_temperature.mirek;
1088
+
1089
+ if (receivedHUEObject.color_temperature !== undefined && receivedHUEObject.color_temperature.mirek !== undefined) {
1090
+ node.updateKNXLightKelvinPercentageState(receivedHUEObject.color_temperature.mirek);
1091
+ node.updateKNXLightKelvinState(receivedHUEObject.color_temperature.mirek);
1092
+ node.currentHUEDevice.color_temperature.mirek = receivedHUEObject.color_temperature.mirek;
957
1093
  }
958
1094
  }
959
1095
  } catch (error) {
@@ -964,8 +1100,10 @@ module.exports = function (RED) {
964
1100
  });
965
1101
  RED.log.error(`knxUltimateHueLight: node.handleSendHUE = (_event): ${error.stack}`);
966
1102
  }
1103
+ // })();
967
1104
  };
968
1105
 
1106
+
969
1107
  // Leave the name after "function", to avoid <anonymous function> in the stack trace, in caso of errors.
970
1108
  node.updateKNXBrightnessState = function updateKNXBrightnessState(_value, _outputtype = "write") {
971
1109
  if (config.GALightBrightnessState !== undefined && config.GALightBrightnessState !== "") {
@@ -1128,11 +1266,11 @@ module.exports = function (RED) {
1128
1266
  // };
1129
1267
 
1130
1268
  /**
1131
- * Update the KNC colors and HSV states group addresses
1132
- * @param {object} _value {xy:{x,y}} in 0-1 scale
1133
- * @param {string} _outputtype "write" is the default KNX command
1134
- * @returns {}
1135
- */
1269
+ * Update the KNC colors and HSV states group addresses
1270
+ * @param {object} _value {xy:{x,y}} in 0-1 scale
1271
+ * @param {string} _outputtype "write" is the default KNX command
1272
+ * @returns {}
1273
+ */
1136
1274
  node.updateKNXLightColorState = function updateKNXLightColorState(_value, _outputtype = "write") {
1137
1275
  if (config.GALightColorState !== undefined && config.GALightColorState !== "") {
1138
1276
  if (_value.xy === undefined || _value.xy.x === undefined) return;
@@ -1229,8 +1367,8 @@ module.exports = function (RED) {
1229
1367
  knxMsgPayload.topic = config.GALightKelvinState;
1230
1368
  knxMsgPayload.dpt = config.dptLightKelvinState;
1231
1369
  if (config.dptLightKelvinState === "7.600") {
1232
- kelvinValue = hueColorConverter.ColorConverter.mirekToKelvin(_value);
1233
- knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(kelvinValue, [2000, 6535], [0, 65535]);
1370
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value);
1371
+ //knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(kelvinValue, [2000, 6535], [0, 65535]);
1234
1372
  } else if (config.dptLightKelvinState === "9.002") {
1235
1373
  knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value);
1236
1374
  }
@@ -1250,7 +1388,7 @@ module.exports = function (RED) {
1250
1388
  fill: "blue",
1251
1389
  shape: "ring",
1252
1390
  text: "HUE->KNX Kelvin",
1253
- payload: kelvinValue,
1391
+ payload: knxMsgPayload.payload,
1254
1392
  });
1255
1393
  }
1256
1394
  }
@@ -1305,6 +1443,6 @@ module.exports = function (RED) {
1305
1443
  }
1306
1444
  done();
1307
1445
  });
1308
- };
1446
+ }
1309
1447
  RED.nodes.registerType("knxUltimateHueLight", knxUltimateHueLight);
1310
1448
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "2.4.28",
6
+ "version": "2.5.1",
7
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",