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 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/>
@@ -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
- This node registers to the Hue Bridge.
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>
@@ -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
- // Find the group
211
- const group = node.hueAllResources.filter((a) => a.id === _groupID)[0];
212
- if (group === null || group === undefined) return;
213
- const owner = node.hueAllResources.filter((a) => a.id === group.owner.rid)[0];
214
- if (owner.children !== undefined && owner.children.length > 0) {
215
- const firstLightId = owner.children.find((a) => a.rtype === "light").rid;
216
- if (firstLightId !== undefined && firstLightId !== null) {
217
- const firstLight = node.hueAllResources.find((a) => a.id === firstLightId);
218
- if (firstLight !== null && firstLight !== undefined) {
219
- return firstLight;
220
- } else {
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
- const hexColor = node.getColorFromHueLight(req.query.id);
427
- res.json(hexColor);
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
- const jKelvin = node.getKelvinFromHueLight(req.query.id);
435
- res.json(jKelvin);
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.error(`Warn KNXUltimateGetResourcesHUE serverNode is null`);
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: true }
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: "no" },
16
+ readStatusAtStartup: { value: "yes" },
17
17
 
18
18
 
19
19
  hueDevice: { value: "" }
20
20
  },
21
21
  inputs: 0,
22
- outputs: 0,
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 = false;
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 === "no"));
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
  });