node-red-contrib-knx-ultimate 2.2.25 → 2.2.27
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 +15 -0
- package/nodes/hue-config.html +7 -9
- package/nodes/hue-config.js +94 -20
- package/nodes/knxUltimate-config.html +1 -1
- package/nodes/knxUltimateHueBattery.html +2 -2
- package/nodes/knxUltimateHueBattery.js +31 -5
- package/nodes/knxUltimateHueButton.js +0 -2
- package/nodes/knxUltimateHueLight.html +403 -289
- package/nodes/knxUltimateHueLight.js +82 -51
- package/nodes/knxUltimateHueLightSensor.html +1 -1
- package/nodes/knxUltimateHueLightSensor.js +32 -2
- package/nodes/knxUltimateHueTemperatureSensor.html +1 -1
- package/nodes/knxUltimateHueTemperatureSensor.js +32 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 2.2.27** - November 2023<br/>
|
|
10
|
+
This is an interim version, to quick fix some issues. Please report any issue with HUE Nodes, on gitHub.<br/>
|
|
11
|
+
- HUE Light: the UI now changes, to adapt to lamp type.<br/>
|
|
12
|
+
- HUE Light: "Get current" color button, now works for grouped light as well, by reading the first light belongin to the group.<br/>
|
|
13
|
+
- WARNING: the new HUE Light options are to be considered **BETA (= in testing with user feedback)**.<br/>
|
|
14
|
+
|
|
15
|
+
**Version 2.2.26** - November 2023<br/>
|
|
16
|
+
This is an interim version, to quick fix some issues. Please report any issue with HUE Nodes, on gitHub.<br/>
|
|
17
|
+
- HUE Light: fixed some spurious node status errors.<br/>
|
|
18
|
+
- HUE Light: now the brightness status is ever transmitted over the KNX bus, whenever the light is switched on.<br/>
|
|
19
|
+
- HUE Battery: the node can respond to KNX read request, by sending the stored value as response to the KNX bus.<br/>
|
|
20
|
+
- HUE light level: the node can respond to KNX read request, by sending the stored value as response to the KNX bus.<br/>
|
|
21
|
+
- HUE temperature sensor: the node can respond to KNX read request, by sending the stored value as response to the KNX bus.<br/>
|
|
22
|
+
- WARNING: the new HUE Light options are to be considered **BETA (= in testing with user feedback)**.<br/>
|
|
23
|
+
|
|
9
24
|
**Version 2.2.25** - November 2023<br/>
|
|
10
25
|
- HUE Light: fixed settings of some behaviour options.<br/>
|
|
11
26
|
- You can now query the HUE Light node stati, via a "read" telegram sent to the KNX stati group address. ("Status" is Latin, not English, so the plural is "stati" and not "statuses" nor "status"). Other HUE nodes will follow asap.<br/>
|
package/nodes/hue-config.html
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
$("#getinfocam").click(function () {
|
|
20
20
|
|
|
21
|
-
var myNotification = RED.notify("Please press the Link button on the HUE Bridge",
|
|
21
|
+
var myNotification = RED.notify("Please press the Link button on the HUE Bridge. Once pressed, click OK.",
|
|
22
22
|
{
|
|
23
23
|
modal: true,
|
|
24
24
|
fixed: true,
|
|
@@ -125,17 +125,15 @@
|
|
|
125
125
|
<label for="node-config-input-clientkey"> Bridge Key</label>
|
|
126
126
|
<input type="password" id="node-config-input-clientkey" placeholder="" disabled>
|
|
127
127
|
</div>
|
|
128
|
+
<div class="form-row">
|
|
129
|
+
<label for="node-config-input-debug"> Bridge Key</label>
|
|
130
|
+
<input rows="20" style="width:100%" type="textarea" id="node-config-input-debug" placeholder="" disabled>
|
|
131
|
+
</div>
|
|
128
132
|
</div>
|
|
129
133
|
|
|
130
134
|
|
|
131
135
|
</script>
|
|
132
|
-
<script type="text/markdown" data-help-name="hue-config"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Just set the Bridge's IP and click **CONNECT** button.
|
|
136
|
-
|
|
137
|
-
[Find it useful?](https://www.paypal.me/techtoday)
|
|
138
|
-
|
|
139
|
-
<br/>
|
|
136
|
+
<script type="text/markdown" data-help-name="hue-config" This node registers to the Hue Bridge. Just set the Bridge's IP
|
|
137
|
+
and click **CONNECT** button. [Find it useful?](https://www.paypal.me/techtoday) <br />
|
|
140
138
|
|
|
141
139
|
</script>
|
package/nodes/hue-config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
1
2
|
/* eslint-disable no-lonely-if */
|
|
2
3
|
/* eslint-disable no-param-reassign */
|
|
3
4
|
/* eslint-disable no-inner-declarations */
|
|
@@ -170,10 +171,28 @@ module.exports = (RED) => {
|
|
|
170
171
|
// Query the HUE Bridge to return the resources
|
|
171
172
|
node.loadResourcesFromHUEBridge = async () => {
|
|
172
173
|
if (node.linkStatus === "disconnected") return;
|
|
173
|
-
//(async () => {
|
|
174
|
+
// (async () => {
|
|
174
175
|
// °°°°°° Load ALL resources
|
|
175
176
|
try {
|
|
176
177
|
node.hueAllResources = await node.hueManager.hueApiV2.get("/resource");
|
|
178
|
+
|
|
179
|
+
// // DEBUG
|
|
180
|
+
// try {
|
|
181
|
+
// const fs = require('fs');
|
|
182
|
+
// const { resolve } = require('path');
|
|
183
|
+
// const content = JSON.stringify(node.hueAllResources);
|
|
184
|
+
// try {
|
|
185
|
+
// fs.writeFileSync('resources.json', content);
|
|
186
|
+
// RED.log.info("******************************* FILE WROTE IN resources.json " + resolve("resources.json"))
|
|
187
|
+
// // file written successfully
|
|
188
|
+
// } catch (error) {
|
|
189
|
+
// RED.log.error("********************************************* const content = JSON.stringify(node.hueAllResources)2222: " + error.message)
|
|
190
|
+
// }
|
|
191
|
+
// } catch (error) {
|
|
192
|
+
// RED.log.error("********************************************* const content = JSON.stringify(node.hueAllResources): " + error.message)
|
|
193
|
+
// }
|
|
194
|
+
|
|
195
|
+
|
|
177
196
|
if (node.hueAllResources !== undefined) {
|
|
178
197
|
node.hueAllRooms = node.hueAllResources.filter((a) => a.type === "room");
|
|
179
198
|
// Update all KNX State of the nodes with the new hue device values
|
|
@@ -206,22 +225,21 @@ module.exports = (RED) => {
|
|
|
206
225
|
};
|
|
207
226
|
|
|
208
227
|
node.getFirstLightInGroup = function getFirstLightInGroup(_groupID) {
|
|
209
|
-
if (node.hueAllResources === undefined) return;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return;
|
|
228
|
+
if (node.hueAllResources === undefined || node.hueAllResources === null) return;
|
|
229
|
+
try {
|
|
230
|
+
const group = node.hueAllResources.filter((a) => a.id === _groupID)[0];
|
|
231
|
+
const owner = node.hueAllResources.filter((a) => a.id === group.owner.rid)[0];
|
|
232
|
+
if (owner.children !== undefined) {
|
|
233
|
+
const dev = node.hueAllResources.filter((a) => a.id === owner.children[0].rid)[0];
|
|
234
|
+
if (dev.type === "device" && dev.services !== undefined) {
|
|
235
|
+
const lightID = dev.services.filter((a) => a.rtype === 'light')[0].rid;
|
|
236
|
+
const oLight = node.hueAllResources.filter((a) => a.id === lightID)[0];
|
|
237
|
+
return oLight;
|
|
238
|
+
} else if (dev.type === "light") {
|
|
239
|
+
return dev;
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
|
-
}
|
|
242
|
+
} catch (error) { }
|
|
225
243
|
};
|
|
226
244
|
|
|
227
245
|
// Returns the cached devices (node.hueAllResources) by type.
|
|
@@ -423,18 +441,64 @@ module.exports = (RED) => {
|
|
|
423
441
|
|
|
424
442
|
RED.httpAdmin.get("/knxUltimateGetHueColor", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
425
443
|
try {
|
|
426
|
-
|
|
427
|
-
|
|
444
|
+
// find wether the light is a light or is grouped_light
|
|
445
|
+
let hexColor;
|
|
446
|
+
const _oDevice = node.hueAllResources.filter((a) => a.id === req.query.id)[0];
|
|
447
|
+
if (_oDevice.type === "light") {
|
|
448
|
+
hexColor = node.getColorFromHueLight(req.query.id);
|
|
449
|
+
} else {
|
|
450
|
+
// grouped_light, get the first light in the group
|
|
451
|
+
const oLight = node.getFirstLightInGroup(_oDevice.id);
|
|
452
|
+
hexColor = node.getColorFromHueLight(oLight.id);
|
|
453
|
+
}
|
|
454
|
+
res.json(hexColor !== undefined ? hexColor : "Select the device first!");
|
|
428
455
|
} catch (error) {
|
|
429
456
|
res.json("Select the device first!");
|
|
430
457
|
}
|
|
431
458
|
});
|
|
432
459
|
RED.httpAdmin.get("/knxUltimateGetKelvinColor", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
433
460
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
461
|
+
// find wether the light is a light or is grouped_light
|
|
462
|
+
let kelvinValue;
|
|
463
|
+
const _oDevice = node.hueAllResources.filter((a) => a.id === req.query.id)[0];
|
|
464
|
+
if (_oDevice.type === "light") {
|
|
465
|
+
kelvinValue = node.getKelvinFromHueLight(req.query.id);
|
|
466
|
+
} else {
|
|
467
|
+
// grouped_light, get the first light in the group
|
|
468
|
+
const oLight = node.getFirstLightInGroup(_oDevice.id);
|
|
469
|
+
kelvinValue = node.getKelvinFromHueLight(oLight.id);
|
|
470
|
+
}
|
|
471
|
+
res.json(kelvinValue !== undefined ? kelvinValue : "Select the device first!");
|
|
436
472
|
} catch (error) {
|
|
437
473
|
res.json("Select the device first!");
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
RED.httpAdmin.get("/knxUltimateGetLightObject", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
478
|
+
try {
|
|
479
|
+
const _lightId = req.query.id;
|
|
480
|
+
const oLight = node.hueAllResources.filter((a) => a.id === _lightId)[0];
|
|
481
|
+
// Infer some useful info, so the HTML part cann avoid to query the server
|
|
482
|
+
// Kelvin
|
|
483
|
+
try {
|
|
484
|
+
if (oLight.color_temperature !== undefined && oLight.color_temperature.mirek !== undefined) {
|
|
485
|
+
oLight.calculatedKelvin = hueColorConverter.ColorConverter.mirekToKelvin(oLight.color_temperature.mirek);
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
oLight.calculatedKelvin = undefined;
|
|
489
|
+
}
|
|
490
|
+
// HEX value from XYBri
|
|
491
|
+
try {
|
|
492
|
+
const retRGB = hueColorConverter.ColorConverter.xyBriToRgb(oLight.color.xy.x, oLight.color.xy.y, oLight.dimming.brightness);
|
|
493
|
+
const ret = "#" + hueColorConverter.ColorConverter.rgbHex(retRGB.r, retRGB.g, retRGB.b).toString();
|
|
494
|
+
oLight.calculatedHEXColor = ret;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
oLight.calculatedHEXColor = undefined;
|
|
497
|
+
}
|
|
498
|
+
res.json(oLight);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`KNXUltimateHue: hueEngine: knxUltimateGetLightObject: error ${error.message}`);
|
|
501
|
+
res.json({});
|
|
438
502
|
}
|
|
439
503
|
});
|
|
440
504
|
|
|
@@ -443,7 +507,7 @@ module.exports = (RED) => {
|
|
|
443
507
|
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
444
508
|
const serverNode = RED.nodes.getNode(req.query.nodeID); // Retrieve node.id of the config node.
|
|
445
509
|
if (serverNode === null) {
|
|
446
|
-
RED.log.
|
|
510
|
+
RED.log.warn(`Warn KNXUltimateGetResourcesHUE serverNode is null`);
|
|
447
511
|
res.json({ devices: `serverNode not set` });
|
|
448
512
|
return;
|
|
449
513
|
}
|
|
@@ -457,12 +521,22 @@ module.exports = (RED) => {
|
|
|
457
521
|
} catch (error) {
|
|
458
522
|
//RED.log.error(`Errore KNXUltimateGetResourcesHUE non gestito ${error.message}`);
|
|
459
523
|
res.json({ devices: error.message });
|
|
524
|
+
RED.log.error(`Err KNXUltimateGetResourcesHUE: ${error.message}`);
|
|
460
525
|
// (async () => {
|
|
461
526
|
// await node.initHUEConnection();
|
|
462
527
|
// })();
|
|
463
528
|
}
|
|
464
529
|
});
|
|
465
530
|
|
|
531
|
+
RED.httpAdmin.get("/knxUltimateGetFirstLightInGroup", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
532
|
+
try {
|
|
533
|
+
res.json(node.getFirstLightInGroup(req.query.id));
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`KNXUltimateHue: hueEngine: knxUltimateGetFirstLightInGroup: error ${error.message}`);
|
|
536
|
+
res.json({});
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
466
540
|
RED.httpAdmin.get("/knxUltimateDpts", RED.auth.needsPermission("hue-config.read"), (req, res) => {
|
|
467
541
|
try {
|
|
468
542
|
const dpts = Object.entries(dptlib).filter(onlyDptKeys).map(extractBaseNo).sort(sortBy("base")).reduce(toConcattedSubtypes, []);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
ignoreTelegramsWithRepeatedFlag: { value: false, required: false },
|
|
22
22
|
keyringFileXML: { value: "" },
|
|
23
23
|
knxSecureSelected: { value: false },
|
|
24
|
-
autoReconnect: { value:
|
|
24
|
+
autoReconnect: { value: "yes" }
|
|
25
25
|
},
|
|
26
26
|
credentials: {
|
|
27
27
|
keyringFilePassword: { type: "password" }
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
namebatterysensor: { value: "" },
|
|
14
14
|
GAbatterysensor: { value: "" },
|
|
15
15
|
dptbatterysensor: { value: "" },
|
|
16
|
-
readStatusAtStartup: { value: "
|
|
16
|
+
readStatusAtStartup: { value: "yes" },
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
hueDevice: { value: "" }
|
|
20
20
|
},
|
|
21
21
|
inputs: 0,
|
|
22
|
-
outputs:
|
|
22
|
+
outputs: 1,
|
|
23
23
|
icon: "node-hue-icon.svg",
|
|
24
24
|
label: function () {
|
|
25
25
|
return (this.name);
|
|
@@ -7,7 +7,7 @@ module.exports = function (RED) {
|
|
|
7
7
|
node.topic = node.name;
|
|
8
8
|
node.name = config.name === undefined ? 'Hue' : config.name;
|
|
9
9
|
node.dpt = '';
|
|
10
|
-
node.notifyreadrequest =
|
|
10
|
+
node.notifyreadrequest = true;
|
|
11
11
|
node.notifyreadrequestalsorespondtobus = 'false';
|
|
12
12
|
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = '';
|
|
13
13
|
node.notifyresponse = false;
|
|
@@ -23,7 +23,9 @@ module.exports = function (RED) {
|
|
|
23
23
|
node.formatnegativevalue = 'leave';
|
|
24
24
|
node.formatdecimalsvalue = 2;
|
|
25
25
|
node.hueDevice = config.hueDevice;
|
|
26
|
-
node.initializingAtStart = !((config.readStatusAtStartup === undefined || config.readStatusAtStartup === "
|
|
26
|
+
node.initializingAtStart = !((config.readStatusAtStartup === undefined || config.readStatusAtStartup === "yes"));
|
|
27
|
+
node.currentDeviceValue = 0;
|
|
28
|
+
|
|
27
29
|
// Used to call the status update from the config node.
|
|
28
30
|
node.setNodeStatus = ({
|
|
29
31
|
fill, shape, text, payload,
|
|
@@ -42,13 +44,22 @@ module.exports = function (RED) {
|
|
|
42
44
|
|
|
43
45
|
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
44
46
|
node.handleSend = (msg) => {
|
|
47
|
+
// Respond to KNX read telegram, by sending the current value as response telegram.
|
|
48
|
+
if (msg.knx.event === "GroupValue_Read") {
|
|
49
|
+
switch (msg.knx.destination) {
|
|
50
|
+
case config.GAbatterysensor:
|
|
51
|
+
// To the KNX bus wires
|
|
52
|
+
node.sendResponseToKNX(node.currentDeviceValue);
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
45
58
|
};
|
|
46
59
|
|
|
47
60
|
node.handleSendHUE = (_event) => {
|
|
48
61
|
try {
|
|
49
62
|
if (_event.id === config.hueDevice) {
|
|
50
|
-
|
|
51
|
-
// IMPORTANT: exit if no event presen.
|
|
52
63
|
if (!_event.hasOwnProperty("power_state") || _event.power_state.battery_level === undefined) return;
|
|
53
64
|
|
|
54
65
|
const knxMsgPayload = {};
|
|
@@ -62,6 +73,7 @@ module.exports = function (RED) {
|
|
|
62
73
|
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id,
|
|
63
74
|
});
|
|
64
75
|
}
|
|
76
|
+
node.currentDeviceValue = knxMsgPayload.payload;
|
|
65
77
|
|
|
66
78
|
// Setup the output msg
|
|
67
79
|
knxMsgPayload.name = node.name;
|
|
@@ -73,13 +85,27 @@ module.exports = function (RED) {
|
|
|
73
85
|
node.setNodeStatusHue({
|
|
74
86
|
fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload,
|
|
75
87
|
});
|
|
76
|
-
|
|
77
88
|
}
|
|
78
89
|
} catch (error) {
|
|
79
90
|
node.status({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${new Date().getDate()}, ${new Date().toLocaleTimeString()})` });
|
|
80
91
|
}
|
|
81
92
|
};
|
|
82
93
|
|
|
94
|
+
|
|
95
|
+
node.sendResponseToKNX = (_level) => {
|
|
96
|
+
const knxMsgPayload = {};
|
|
97
|
+
knxMsgPayload.topic = config.GAbatterysensor;
|
|
98
|
+
knxMsgPayload.dpt = config.dptbatterysensor;
|
|
99
|
+
|
|
100
|
+
knxMsgPayload.payload = _level;
|
|
101
|
+
// Send to KNX bus
|
|
102
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
103
|
+
node.server.writeQueueAdd({
|
|
104
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response', nodecallerid: node.id,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
83
109
|
// On each deploy, unsubscribe+resubscribe
|
|
84
110
|
if (node.server) {
|
|
85
111
|
node.server.removeClient(node);
|
|
@@ -194,8 +194,6 @@ module.exports = function (RED) {
|
|
|
194
194
|
node.server.writeQueueAdd({
|
|
195
195
|
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id,
|
|
196
196
|
});
|
|
197
|
-
}
|
|
198
|
-
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
199
197
|
node.setNodeStatusHue({
|
|
200
198
|
fill: 'grey', shape: 'ring', text: 'HUE->KNX STOP DIM', payload: knxMsgPayload.payload,
|
|
201
199
|
});
|