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 +9 -0
- package/KNXEngine/src/dptlib/dpt9.js +12 -2
- package/nodes/knxUltimateHueLight.html +4 -2
- package/nodes/knxUltimateHueLight.js +3 -2
- package/nodes/knxUltimateHueMotion.js +0 -2
- package/nodes/knxUltimateHueScene.html +56 -2
- package/nodes/knxUltimateHueScene.js +33 -3
- package/nodes/utils/hueColorConverter.js +2 -2
- package/package.json +1 -1
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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.
|
|
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
|
-
//
|
|
119
|
-
|
|
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.
|
|
27
|
+
return Math.floor(1000000 / _kelvin);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
static mirekToKelvin(_mirek) {
|
|
31
|
-
return Math.
|
|
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.
|
|
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",
|