node-red-contrib-knx-ultimate 2.2.30 → 2.2.32

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,15 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 2.2.32** - December 2023<br/>
10
+ - Quickfix: HUE Light: fixed an issue in the conversion of tunable white from 9.002 to mired and vice versa.<br/>
11
+ - WARNING: the new HUE Scene node is to be considered **BETA (= in testing with user feedback)**.<br/>
12
+
13
+ **Version 2.2.31** - December 2023<br/>
14
+ - NEW: HUE Scene node: added the status GA and Datapoint, for the scene to send true/false if active/not active. This currently works only for "Single mode".<br/>
15
+ - WARNING: the new HUE Scene node is to be considered **BETA (= in testing with user feedback)**.<br/>
16
+
17
+
9
18
  **Version 2.2.30** - December 2023<br/>
10
19
  - NEW: HUE Scene node: added a "Multi scene" section, more powerful.<br/>
11
20
  - HUE Scene: when selecting a group address for the scene, the scene number dropdown list doesn't show up.<br/>
@@ -11,7 +11,7 @@ const knxLog = require('./../KnxLog')
11
11
 
12
12
  const util = require('util')
13
13
  // kudos to http://croquetweak.blogspot.gr/2014/08/deconstructing-floats-frexp-and-ldexp.html
14
- function ldexp (mantissa, exponent) {
14
+ function ldexp(mantissa, exponent) {
15
15
  return exponent > 1023 // avoid multiplying by infinity
16
16
  ? mantissa * Math.pow(2, 1023) * Math.pow(2, exponent - 1023)
17
17
  : exponent < -1074 // avoid multiplying by zero
@@ -19,7 +19,7 @@ function ldexp (mantissa, exponent) {
19
19
  : mantissa * Math.pow(2, exponent)
20
20
  }
21
21
 
22
- function frexp (value) {
22
+ function frexp(value) {
23
23
  if (value === 0) return [value, 0]
24
24
  const data = new DataView(new ArrayBuffer(8))
25
25
  data.setFloat64(0, value)
@@ -62,6 +62,16 @@ exports.fromBuffer = function (buf) {
62
62
  knxLog.get().warn('DPT9.fromBuffer: buf should be 2 bytes long (got %d bytes)', buf.length)
63
63
  return null
64
64
  } else {
65
+
66
+ // Homeassistant:
67
+ // let data = (buf[0] * 256) + buf[1]
68
+ // let esponente = (data >> 11) & 0x0F
69
+ // let significand = data & 0x7FF
70
+ // let segno = data >> 15
71
+ // if (segno === 1) { significand = significand - 2048 }
72
+ // let value = Number.parseFloat(significand << esponente) / 100
73
+ // return value;
74
+
65
75
  const sign = buf[0] >> 7
66
76
  const exponent = (buf[0] & 0b01111000) >> 3
67
77
  let mantissa = 256 * (buf[0] & 0b00000111) + buf[1]
@@ -70,9 +70,11 @@
70
70
  if (dpt.value.startsWith(_dpt)) {
71
71
  // Adjustment for HUE Temperature
72
72
  if (dpt.value.startsWith("7.600")) {
73
- $(_destinationWidget).append($("<option></option>").attr("value", dpt.value).text(dpt.text + " - KNX Kelvin range (0K-65535K)"));
73
+ $(_destinationWidget).append($("<option></option>").attr("value", dpt.value).text(dpt.text + " - KNX Kelvin range 0-65535k (Homeassistant color_temperature_mode: absolute)"));
74
74
  } else if (dpt.value.startsWith("9.002")) {
75
- $(_destinationWidget).append($("<option></option>").attr("value", dpt.value).text(dpt.text + " - HUE Kelvin range (Default 2000K-6535K"));
75
+ $(_destinationWidget).append($("<option></option>").attr("value", dpt.value).text(dpt.text + " - HUE Kelvin range 2000-6535k (Homeassistant color_temperature_mode: absolute_float)"));
76
+ } else if (dpt.value.startsWith("5.001")) {
77
+ $(_destinationWidget).append($("<option></option>").attr("value", dpt.value).text(dpt.text + " - Homeassistant color_temperature_mode: relative"));
76
78
  } else {
77
79
  $(_destinationWidget).append($("<option></option>").attr("value", dpt.value).text(dpt.text));
78
80
  }
@@ -184,7 +184,7 @@ module.exports = function (RED) {
184
184
  // Relative temperature in Kelvin. Use HUE scale.
185
185
  if (msg.payload > 6535) msg.payload = 6535;
186
186
  if (msg.payload < 2000) msg.payload = 2000;
187
- retMirek = hueColorConverter.ColorConverter.scale(msg.payload, [2000, 6535], [500, 153]);
187
+ retMirek = hueColorConverter.ColorConverter.kelvinToMirek(msg.payload);
188
188
  }
189
189
  state = { color_temperature: { mirek: retMirek } };
190
190
  node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, state, node.isGrouped_light === false ? "setLight" : "setGroupedLight");
@@ -739,7 +739,8 @@ module.exports = function (RED) {
739
739
  if (config.dptLightKelvinState === "7.600") {
740
740
  knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(_value, [153, 500], [65535, 0]);
741
741
  } else if (config.dptLightKelvinState === "9.002") {
742
- knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(_value, [153, 500], [6535, 2000]);
742
+ //knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(_value, [153, 500], [6535, 2000]);
743
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value);
743
744
  }
744
745
  // Send to KNX bus
745
746
  if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined) {
@@ -43,8 +43,6 @@ module.exports = function (RED) {
43
43
  if (_event.id === config.hueDevice) {
44
44
 
45
45
  if (!_event.hasOwnProperty("motion") || _event.motion.motion === undefined) return;
46
-
47
-
48
46
  const knxMsgPayload = {};
49
47
  knxMsgPayload.topic = config.GAmotion;
50
48
  knxMsgPayload.dpt = config.dptmotion;
@@ -15,6 +15,9 @@
15
15
  GAscene: { value: "" },
16
16
  dptscene: { value: "" },
17
17
  valscene: { value: 0 }, // the scene number or true/false
18
+ namesceneStatus: { value: "" },
19
+ GAsceneStatus: { value: "" },
20
+ dptsceneStatus: { value: "" },
18
21
 
19
22
  enableNodePINS: { value: "no" },
20
23
  outputs: { value: 0 },
@@ -39,7 +42,7 @@
39
42
  if (Number(this.selectedModeTabNumber) === 0) return this.name;
40
43
  if (Number(this.selectedModeTabNumber) === 1) return this.namesceneMulti;
41
44
  },
42
- paletteLabel: "Hue Scene",
45
+ paletteLabel: "Hue Scene (Beta)",
43
46
  // button: {
44
47
  // enabled: function() {
45
48
  // // return whether or not the button is enabled, based on the current
@@ -105,8 +108,14 @@
105
108
  .attr("value", dpt.value)
106
109
  .text(dpt.text))
107
110
  }
111
+ if (dpt.value.startsWith("1.")) {
112
+ $("#node-input-dptsceneStatus").append($("<option></option>")
113
+ .attr("value", dpt.value)
114
+ .text(dpt.text))
115
+ }
108
116
  });
109
117
  $("#node-input-dptscene").val(this.dptscene)
118
+ $("#node-input-dptsceneStatus").val(this.dptsceneStatus)
110
119
  $("#node-input-dptsceneMulti").val(this.dptsceneMulti)
111
120
  ShowHideValScene();
112
121
  })
@@ -145,6 +154,39 @@
145
154
  ShowHideValScene();
146
155
  }
147
156
  });
157
+ $("#node-input-GAsceneStatus").autocomplete({
158
+ minLength: 1,
159
+ source: function (request, response) {
160
+ //$.getJSON("csv", request, function( data, status, xhr ) {
161
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
162
+ response($.map(data, function (value, key) {
163
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
164
+ if (fullSearch(sSearch, request.term)) {
165
+ if (value.dpt.startsWith("1.")) {
166
+ return {
167
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
168
+ value: value.ga // Value
169
+ }
170
+ } else { return null; }
171
+ } else {
172
+ return null;
173
+ }
174
+ }));
175
+ });
176
+ }, select: function (event, ui) {
177
+ // Sets Datapoint and device name automatically
178
+ var sDevName = ui.item.label.split("#")[1].trim();
179
+ try {
180
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
181
+ } catch (error) {
182
+ }
183
+ $('#node-input-namesceneStatus').val(sDevName);
184
+ var optVal = $("#node-input-dptsceneStatus option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
185
+ // Select the option value
186
+ $("#node-input-dptsceneStatus").val(optVal);
187
+ ShowHideValScene();
188
+ }
189
+ });
148
190
  $("#node-input-GAsceneMulti").autocomplete({
149
191
  minLength: 1,
150
192
  source: function (request, response) {
@@ -460,12 +502,23 @@
460
502
  <label for="node-input-namescene" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
461
503
  <input type="text" id="node-input-namescene" style="width:200px;margin-left: 5px; text-align: left;">
462
504
  </div>
463
-
464
505
  <div class="form-row" id="divValScene" hidden>
465
506
  <label for="node-input-valscene" style="width:100px;"></label>
466
507
  <label for="node-input-valscene" style="width:20px;">#</label>
467
508
  <select id="node-input-valscene" style="width:180px;margin-left: 5px; text-align: left;"></select>
468
509
  </div>
510
+ <div class="form-row">
511
+ <label for="node-input-namesceneStatus" style="width:100px;"><i class="fa fa-play-circle-o"></i> Status</label>
512
+
513
+ <label for="node-input-GAsceneStatus" style="width:20px;">GA</label>
514
+ <input type="text" id="node-input-GAsceneStatus" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
515
+
516
+ <label for="node-input-dptsceneStatus" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
517
+ <select id="node-input-dptsceneStatus" style="width:140px;"></select>
518
+
519
+ <label for="node-input-namesceneStatus" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
520
+ <input type="text" id="node-input-namesceneStatus" style="width:200px;margin-left: 5px; text-align: left;">
521
+ </div>
469
522
  <br/>
470
523
  <br/>
471
524
  </div> <!-- // End Tab 1 -->
@@ -541,6 +594,7 @@ This works also with the HUE scene text field.
541
594
  |--|--|
542
595
  | Recall | Choose your group address to be used for recalling the HUE scene. In case of Datapoint 1.x, send *true* to that group address to recall the scene, *false* to switch off all lights belonging to the scene. |
543
596
  | # | Select the KNX scene number. Visible only with datapoint 18.001. |
597
+ | Status | It's the scene status. *True* when the scene is active, *false* when the scene is not active. |
544
598
 
545
599
 
546
600
  **Multi mode tab**
@@ -114,9 +114,39 @@ module.exports = function (RED) {
114
114
 
115
115
  node.handleSendHUE = (_event) => {
116
116
  try {
117
- if (_event.id === config.hueDevice) {
118
- // Output the msg to the flow
119
- node.send(_event);
117
+ if (Number(config.selectedModeTabNumber) === 0 && config.hueDevice !== undefined && _event.id === config.hueDevice) {
118
+ // Single mode
119
+ const knxMsgPayload = {};
120
+ knxMsgPayload.topic = config.GAsceneStatus;
121
+ knxMsgPayload.dpt = config.dptsceneStatus;
122
+ if (_event.hasOwnProperty("status") && _event.status.hasOwnProperty("active")) {
123
+ knxMsgPayload.payload = _event.status.active !== "inactive";
124
+ // Send to KNX bus
125
+ if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined && node.server !== undefined) {
126
+ node.server.writeQueueAdd({
127
+ grpaddr: knxMsgPayload.topic,
128
+ payload: knxMsgPayload.payload,
129
+ dpt: knxMsgPayload.dpt,
130
+ outputtype: "write",
131
+ nodecallerid: node.id,
132
+ });
133
+ }
134
+ node.status({
135
+ fill: "blue",
136
+ shape: "dot",
137
+ text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${new Date().getDate()}, ${new Date().toLocaleTimeString()})`,
138
+ });
139
+ // Output the msg to the flow
140
+ node.send(_event);
141
+ }
142
+ } else if (Number(config.selectedModeTabNumber) === 1 && config.rules !== undefined) {
143
+ // Multi mode
144
+ config.rules.forEach(row => {
145
+ if (row.rowRuleHUESceneID !== undefined && _event.id === row.rowRuleHUESceneID) {
146
+ // Output the msg to the flow
147
+ node.send(_event);
148
+ }
149
+ });
120
150
  }
121
151
  } catch (error) {
122
152
  node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
@@ -24,11 +24,11 @@ class ColorConverter {
24
24
  }
25
25
 
26
26
  static kelvinToMirek(_kelvin) {
27
- return Math.round(1000000 / _kelvin, 0);
27
+ return Math.floor(1000000 / _kelvin);
28
28
  }
29
29
 
30
30
  static mirekToKelvin(_mirek) {
31
- return Math.round(1000000 / _mirek, 0);
31
+ return Math.floor(1000000 / _mirek);
32
32
  }
33
33
 
34
34
  // Linear interpolation of input y given starting and ending ranges
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "2.2.30",
6
+ "version": "2.2.32",
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",