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 +23 -3
- package/README.md +5 -2
- package/nodes/hue-config.js +62 -3
- package/nodes/knxUltimateHueLight.html +15 -2
- package/nodes/knxUltimateHueLight.js +201 -63
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,13 +6,33 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
-
**Version 2.
|
|
10
|
-
-
|
|
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 |  |
|
|
56
59
|
| KNX Routing |  |
|
|
57
60
|
| KNX Secure Tunnelling |  |
|
|
58
61
|
| KNX Secure Routing |  |
|
|
59
|
-
| KNX 3rd PARTY IOT API |  |
|
|
60
|
-
|
|
62
|
+
| KNX 3rd PARTY IOT API client |  |
|
|
63
|
+
| Matter |  |
|
|
61
64
|
|
|
62
65
|
<br/>
|
|
63
66
|
|
package/nodes/hue-config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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(
|
|
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
|
-
|
|
897
|
-
|
|
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 (
|
|
1048
|
+
if (receivedHUEObject.on.on === false && (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === "onhueoff")) {
|
|
916
1049
|
node.updateKNXBrightnessState(0);
|
|
917
|
-
if (
|
|
918
|
-
} else if (
|
|
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
|
-
|
|
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 =
|
|
1059
|
+
node.currentHUEDevice.on.on = receivedHUEObject.on.on;
|
|
925
1060
|
}
|
|
926
1061
|
|
|
927
|
-
if (
|
|
928
|
-
node.updateKNXLightColorState(
|
|
929
|
-
node.currentHUEDevice.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 (
|
|
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 (
|
|
936
|
-
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false &&
|
|
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 && (
|
|
940
|
-
node.updateKNXBrightnessState(
|
|
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 (
|
|
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 =
|
|
1085
|
+
node.currentHUEDevice.dimming.brightness = receivedHUEObject.dimming.brightness;
|
|
951
1086
|
}
|
|
952
1087
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
node.
|
|
956
|
-
node.
|
|
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
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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",
|