node-red-contrib-knx-ultimate 2.1.61 → 2.1.62
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 +4 -0
- package/nodes/knxUltimateHueLight.js +1 -2
- package/nodes/utils/hueColorConverter.js +122 -119
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
<p>
|
|
9
|
+
<b>Version 2.1.62</b> - October 2023<br/>
|
|
10
|
+
- HUE Light: FIX: a typo error could prevent the light to switch on, if the light was set to specified RGB color at switch on.<br/>
|
|
11
|
+
</p>
|
|
12
|
+
<p>
|
|
9
13
|
<b>Version 2.1.61</b> - October 2023<br/>
|
|
10
14
|
- HUE Light: NEW color picker. A Color picker is shown in the right TABS of node-red, as soon as you open the light node. You can choose the color you want, and paste the RGB color into the HUE light node.<br/>
|
|
11
15
|
- HUE Light: the color getter is now not shown, whenever you select a grouped light. You can use the color picker instead.<br/>
|
|
@@ -73,6 +73,7 @@ module.exports = function (RED) {
|
|
|
73
73
|
if (node.DayTime === true && (config.specifySwitchOnBrightness === undefined || config.specifySwitchOnBrightness === "yes")) {
|
|
74
74
|
try {
|
|
75
75
|
jColorChoosen = JSON.parse(config.colorAtSwitchOnDayTime);
|
|
76
|
+
if (jColorChoosen.green === undefined) jColorChoosen.green = jColorChoosen.hasOwnProperty("geen") ? jColorChoosen.geen : jColorChoosen.green;
|
|
76
77
|
} catch (error) {
|
|
77
78
|
jColorChoosen = { red: 255, green: 255, blue: 255 };
|
|
78
79
|
}
|
|
@@ -431,9 +432,7 @@ module.exports = function (RED) {
|
|
|
431
432
|
// To avoid wrongly turn light state on, exit
|
|
432
433
|
if (_event.dimming.brightness < 1) _event.dimming.brightness = 0;
|
|
433
434
|
if (node.currentHUEDevice.hasOwnProperty('on') && node.currentHUEDevice.on.on === false && _event.dimming.brightness === 0) return;
|
|
434
|
-
// if (config.specifySwitchOnBrightness === undefined || config.specifySwitchOnBrightness === "yes") {
|
|
435
435
|
if (node.currentHUEDevice.on.on === false) node.updateKNXLightState(_event.dimming.brightness > 0);
|
|
436
|
-
// }
|
|
437
436
|
node.updateKNXBrightnessState(_event.dimming.brightness);
|
|
438
437
|
if (node.currentHUEDevice !== undefined) node.currentHUEDevice.dimming = _event.dimming; // Update the internal object representing the current light
|
|
439
438
|
// If the brightness reaches zero, the hue lamp "on" property must be set to zero as well
|
|
@@ -1,241 +1,244 @@
|
|
|
1
|
-
const convert = require('color-convert')
|
|
1
|
+
const convert = require('color-convert');
|
|
2
|
+
|
|
2
3
|
class ColorConverter {
|
|
3
|
-
static getGamutRanges
|
|
4
|
+
static getGamutRanges() {
|
|
4
5
|
const gamutA = {
|
|
5
6
|
red: [0.704, 0.296],
|
|
6
7
|
green: [0.2151, 0.7106],
|
|
7
|
-
blue: [0.138, 0.08]
|
|
8
|
-
}
|
|
8
|
+
blue: [0.138, 0.08],
|
|
9
|
+
};
|
|
9
10
|
|
|
10
11
|
const gamutB = {
|
|
11
12
|
red: [0.675, 0.322],
|
|
12
13
|
green: [0.409, 0.518],
|
|
13
|
-
blue: [0.167, 0.04]
|
|
14
|
-
}
|
|
14
|
+
blue: [0.167, 0.04],
|
|
15
|
+
};
|
|
15
16
|
|
|
16
17
|
const gamutC = {
|
|
17
18
|
red: [0.692, 0.308],
|
|
18
19
|
green: [0.17, 0.7],
|
|
19
|
-
blue: [0.153, 0.048]
|
|
20
|
-
}
|
|
20
|
+
blue: [0.153, 0.048],
|
|
21
|
+
};
|
|
21
22
|
|
|
22
23
|
const defaultGamut = {
|
|
23
24
|
red: [1.0, 0],
|
|
24
25
|
green: [0.0, 1.0],
|
|
25
|
-
blue: [0.0, 0.0]
|
|
26
|
-
}
|
|
26
|
+
blue: [0.0, 0.0],
|
|
27
|
+
};
|
|
27
28
|
|
|
28
|
-
return {
|
|
29
|
+
return {
|
|
30
|
+
gamutA, gamutB, gamutC, default: defaultGamut,
|
|
31
|
+
};
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
static getLightColorGamutRange
|
|
32
|
-
const ranges = ColorConverter.getGamutRanges()
|
|
33
|
-
const gamutA = ranges
|
|
34
|
-
const gamutB = ranges
|
|
35
|
-
const gamutC = ranges
|
|
34
|
+
static getLightColorGamutRange(gamutTypeABC = null) {
|
|
35
|
+
const ranges = ColorConverter.getGamutRanges();
|
|
36
|
+
const { gamutA } = ranges;
|
|
37
|
+
const { gamutB } = ranges;
|
|
38
|
+
const { gamutC } = ranges;
|
|
36
39
|
|
|
37
40
|
const philipsModels = {
|
|
38
41
|
A: gamutA,
|
|
39
42
|
B: gamutB,
|
|
40
|
-
C: gamutC
|
|
41
|
-
}
|
|
43
|
+
C: gamutC,
|
|
44
|
+
};
|
|
42
45
|
|
|
43
46
|
if (philipsModels[gamutTypeABC]) {
|
|
44
|
-
return philipsModels[gamutTypeABC]
|
|
47
|
+
return philipsModels[gamutTypeABC];
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
return ranges.default
|
|
50
|
+
return ranges.default;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
static rgbToXy
|
|
51
|
-
function getGammaCorrectedValue
|
|
52
|
-
return (value > 0.04045) ?
|
|
53
|
+
static rgbToXy(red, green, blue, gamutTypeABC = null) {
|
|
54
|
+
function getGammaCorrectedValue(value) {
|
|
55
|
+
return (value > 0.04045) ? ((value + 0.055) / (1.0 + 0.055)) ** 2.4 : (value / 12.92);
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
const colorGamut = ColorConverter.getLightColorGamutRange(gamutTypeABC)
|
|
58
|
+
const colorGamut = ColorConverter.getLightColorGamutRange(gamutTypeABC);
|
|
56
59
|
|
|
57
|
-
red = parseFloat(red / 255)
|
|
58
|
-
green = parseFloat(green / 255)
|
|
59
|
-
blue = parseFloat(blue / 255)
|
|
60
|
+
red = parseFloat(red / 255);
|
|
61
|
+
green = parseFloat(green / 255);
|
|
62
|
+
blue = parseFloat(blue / 255);
|
|
60
63
|
|
|
61
|
-
red = getGammaCorrectedValue(red)
|
|
62
|
-
green = getGammaCorrectedValue(green)
|
|
63
|
-
blue = getGammaCorrectedValue(blue)
|
|
64
|
+
red = getGammaCorrectedValue(red);
|
|
65
|
+
green = getGammaCorrectedValue(green);
|
|
66
|
+
blue = getGammaCorrectedValue(blue);
|
|
64
67
|
|
|
65
|
-
const x = red * 0.649926 + green * 0.103455 + blue * 0.197109
|
|
66
|
-
const y = red * 0.234327 + green * 0.743075 + blue * 0.022598
|
|
67
|
-
const z = red * 0.0000000 + green * 0.053077 + blue * 1.035763
|
|
68
|
+
const x = red * 0.649926 + green * 0.103455 + blue * 0.197109;
|
|
69
|
+
const y = red * 0.234327 + green * 0.743075 + blue * 0.022598;
|
|
70
|
+
const z = red * 0.0000000 + green * 0.053077 + blue * 1.035763;
|
|
68
71
|
|
|
69
72
|
let xy = {
|
|
70
73
|
x: x / (x + y + z),
|
|
71
|
-
y: y / (x + y + z)
|
|
72
|
-
}
|
|
74
|
+
y: y / (x + y + z),
|
|
75
|
+
};
|
|
73
76
|
|
|
74
77
|
if (!ColorConverter.xyIsInGamutRange(xy, colorGamut)) {
|
|
75
|
-
xy = ColorConverter.getClosestColor(xy, colorGamut)
|
|
78
|
+
xy = ColorConverter.getClosestColor(xy, colorGamut);
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
return xy
|
|
81
|
+
return xy;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
static getBrightnessFromRGB
|
|
82
|
-
const hsv = convert.rgb.hsv(red, green, blue)
|
|
83
|
-
const brightness = hsv[2]
|
|
84
|
-
return brightness
|
|
84
|
+
static getBrightnessFromRGB(red, green, blue) {
|
|
85
|
+
const hsv = convert.rgb.hsv(red, green, blue);
|
|
86
|
+
const brightness = hsv[2];
|
|
87
|
+
return brightness;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
static convert_1_255_ToPercentage
|
|
88
|
-
const percentage = (number / 255) * 100
|
|
89
|
-
return percentage
|
|
90
|
+
static convert_1_255_ToPercentage(number) {
|
|
91
|
+
const percentage = (number / 255) * 100;
|
|
92
|
+
return percentage;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
static xyIsInGamutRange
|
|
93
|
-
gamut = gamut || ColorConverter.getGamutRanges().gamutC
|
|
95
|
+
static xyIsInGamutRange(xy, gamut) {
|
|
96
|
+
gamut = gamut || ColorConverter.getGamutRanges().gamutC;
|
|
94
97
|
if (Array.isArray(xy)) {
|
|
95
98
|
xy = {
|
|
96
99
|
x: xy[0],
|
|
97
|
-
y: xy[1]
|
|
98
|
-
}
|
|
100
|
+
y: xy[1],
|
|
101
|
+
};
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
const v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]]
|
|
102
|
-
const v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]]
|
|
103
|
-
const v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]]
|
|
104
|
+
const v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]];
|
|
105
|
+
const v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]];
|
|
106
|
+
const v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]];
|
|
104
107
|
|
|
105
|
-
const dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1])
|
|
106
|
-
const dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1])
|
|
107
|
-
const dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1])
|
|
108
|
-
const dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1])
|
|
109
|
-
const dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1])
|
|
108
|
+
const dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]);
|
|
109
|
+
const dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]);
|
|
110
|
+
const dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]);
|
|
111
|
+
const dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]);
|
|
112
|
+
const dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]);
|
|
110
113
|
|
|
111
|
-
const invDenom = 1 / (dot00 * dot11 - dot01 * dot01)
|
|
114
|
+
const invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
|
|
112
115
|
|
|
113
|
-
const u = (dot11 * dot02 - dot01 * dot12) * invDenom
|
|
114
|
-
const v = (dot00 * dot12 - dot01 * dot02) * invDenom
|
|
116
|
+
const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
|
117
|
+
const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
|
115
118
|
|
|
116
|
-
return ((u >= 0) && (v >= 0) && (u + v < 1))
|
|
119
|
+
return ((u >= 0) && (v >= 0) && (u + v < 1));
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
static getClosestColor
|
|
120
|
-
function getLineDistance
|
|
121
|
-
return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y)
|
|
122
|
+
static getClosestColor(xy, gamut) {
|
|
123
|
+
function getLineDistance(pointA, pointB) {
|
|
124
|
+
return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
function getClosestPoint
|
|
125
|
-
const xy2a = [xy.x - pointA.x, xy.y - pointA.y]
|
|
126
|
-
const a2b = [pointB.x - pointA.x, pointB.y - pointA.y]
|
|
127
|
-
const a2bSqr =
|
|
128
|
-
const xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1]
|
|
129
|
-
const t = xy2a_dot_a2b / a2bSqr
|
|
127
|
+
function getClosestPoint(xy, pointA, pointB) {
|
|
128
|
+
const xy2a = [xy.x - pointA.x, xy.y - pointA.y];
|
|
129
|
+
const a2b = [pointB.x - pointA.x, pointB.y - pointA.y];
|
|
130
|
+
const a2bSqr = a2b[0] ** 2 + a2b[1] ** 2;
|
|
131
|
+
const xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1];
|
|
132
|
+
const t = xy2a_dot_a2b / a2bSqr;
|
|
130
133
|
|
|
131
134
|
return {
|
|
132
135
|
x: pointA.x + a2b[0] * t,
|
|
133
|
-
y: pointA.y + a2b[1] * t
|
|
134
|
-
}
|
|
136
|
+
y: pointA.y + a2b[1] * t,
|
|
137
|
+
};
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
const greenBlue = {
|
|
138
141
|
a: {
|
|
139
142
|
x: gamut.green[0],
|
|
140
|
-
y: gamut.green[1]
|
|
143
|
+
y: gamut.green[1],
|
|
141
144
|
},
|
|
142
145
|
b: {
|
|
143
146
|
x: gamut.blue[0],
|
|
144
|
-
y: gamut.blue[1]
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
+
y: gamut.blue[1],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
147
150
|
|
|
148
151
|
const greenRed = {
|
|
149
152
|
a: {
|
|
150
153
|
x: gamut.green[0],
|
|
151
|
-
y: gamut.green[1]
|
|
154
|
+
y: gamut.green[1],
|
|
152
155
|
},
|
|
153
156
|
b: {
|
|
154
157
|
x: gamut.red[0],
|
|
155
|
-
y: gamut.red[1]
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
+
y: gamut.red[1],
|
|
159
|
+
},
|
|
160
|
+
};
|
|
158
161
|
|
|
159
162
|
const blueRed = {
|
|
160
163
|
a: {
|
|
161
164
|
x: gamut.red[0],
|
|
162
|
-
y: gamut.red[1]
|
|
165
|
+
y: gamut.red[1],
|
|
163
166
|
},
|
|
164
167
|
b: {
|
|
165
168
|
x: gamut.blue[0],
|
|
166
|
-
y: gamut.blue[1]
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
+
y: gamut.blue[1],
|
|
170
|
+
},
|
|
171
|
+
};
|
|
169
172
|
|
|
170
173
|
const closestColorPoints = {
|
|
171
174
|
greenBlue: getClosestPoint(xy, greenBlue.a, greenBlue.b),
|
|
172
175
|
greenRed: getClosestPoint(xy, greenRed.a, greenRed.b),
|
|
173
|
-
blueRed: getClosestPoint(xy, blueRed.a, blueRed.b)
|
|
174
|
-
}
|
|
176
|
+
blueRed: getClosestPoint(xy, blueRed.a, blueRed.b),
|
|
177
|
+
};
|
|
175
178
|
|
|
176
179
|
const distance = {
|
|
177
180
|
greenBlue: getLineDistance(xy, closestColorPoints.greenBlue),
|
|
178
181
|
greenRed: getLineDistance(xy, closestColorPoints.greenRed),
|
|
179
|
-
blueRed: getLineDistance(xy, closestColorPoints.blueRed)
|
|
180
|
-
}
|
|
182
|
+
blueRed: getLineDistance(xy, closestColorPoints.blueRed),
|
|
183
|
+
};
|
|
181
184
|
|
|
182
|
-
let closestDistance
|
|
183
|
-
let closestColor
|
|
185
|
+
let closestDistance;
|
|
186
|
+
let closestColor;
|
|
184
187
|
for (const i in distance) {
|
|
185
188
|
if (distance.hasOwnProperty(i)) {
|
|
186
189
|
if (!closestDistance) {
|
|
187
|
-
closestDistance = distance[i]
|
|
188
|
-
closestColor = i
|
|
190
|
+
closestDistance = distance[i];
|
|
191
|
+
closestColor = i;
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
if (closestDistance > distance[i]) {
|
|
192
|
-
closestDistance = distance[i]
|
|
193
|
-
closestColor = i
|
|
195
|
+
closestDistance = distance[i];
|
|
196
|
+
closestColor = i;
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
|
-
return closestColorPoints[closestColor]
|
|
200
|
+
return closestColorPoints[closestColor];
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
static xyBriToRgb
|
|
201
|
-
function getReversedGammaCorrectedValue
|
|
202
|
-
return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) *
|
|
203
|
+
static xyBriToRgb(x, y, bri) {
|
|
204
|
+
function getReversedGammaCorrectedValue(value) {
|
|
205
|
+
return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * value ** (1.0 / 2.4) - 0.055;
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
const z = 1.0 - x - y
|
|
206
|
-
const Y = bri / 255
|
|
207
|
-
const X = (Y / y) * x
|
|
208
|
-
const Z = (Y / y) * z
|
|
209
|
-
let r = X * 1.612 - Y * 0.203 - Z * 0.302
|
|
210
|
-
let g = -X * 0.509 + Y * 1.412 + Z * 0.066
|
|
211
|
-
let b = X * 0.026 - Y * 0.072 + Z * 0.962
|
|
208
|
+
const z = 1.0 - x - y;
|
|
209
|
+
const Y = bri / 255;
|
|
210
|
+
const X = (Y / y) * x;
|
|
211
|
+
const Z = (Y / y) * z;
|
|
212
|
+
let r = X * 1.612 - Y * 0.203 - Z * 0.302;
|
|
213
|
+
let g = -X * 0.509 + Y * 1.412 + Z * 0.066;
|
|
214
|
+
let b = X * 0.026 - Y * 0.072 + Z * 0.962;
|
|
212
215
|
|
|
213
|
-
r = getReversedGammaCorrectedValue(r)
|
|
214
|
-
g = getReversedGammaCorrectedValue(g)
|
|
215
|
-
b = getReversedGammaCorrectedValue(b)
|
|
216
|
+
r = getReversedGammaCorrectedValue(r);
|
|
217
|
+
g = getReversedGammaCorrectedValue(g);
|
|
218
|
+
b = getReversedGammaCorrectedValue(b);
|
|
216
219
|
|
|
217
220
|
// Bring all negative components to zero
|
|
218
|
-
r = Math.max(r, 0)
|
|
219
|
-
g = Math.max(g, 0)
|
|
220
|
-
b = Math.max(b, 0)
|
|
221
|
+
r = Math.max(r, 0);
|
|
222
|
+
g = Math.max(g, 0);
|
|
223
|
+
b = Math.max(b, 0);
|
|
221
224
|
|
|
222
225
|
// If one component is greater than 1, weight components by that value
|
|
223
|
-
const max = Math.max(r, g, b)
|
|
226
|
+
const max = Math.max(r, g, b);
|
|
224
227
|
if (max > 1) {
|
|
225
|
-
r
|
|
226
|
-
g
|
|
227
|
-
b
|
|
228
|
+
r /= max;
|
|
229
|
+
g /= max;
|
|
230
|
+
b /= max;
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
return {
|
|
231
234
|
red: Math.floor(r * 255),
|
|
232
|
-
|
|
233
|
-
blue: Math.floor(b * 255)
|
|
234
|
-
}
|
|
235
|
+
green: Math.floor(g * 255),
|
|
236
|
+
blue: Math.floor(b * 255),
|
|
237
|
+
};
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
// Linear interpolation of input y given starting and ending ranges
|
|
238
|
-
static scale(y, range1 = [0,100], range2 = [0,255]) {
|
|
241
|
+
static scale(y, range1 = [0, 100], range2 = [0, 255]) {
|
|
239
242
|
const [xMin, xMax] = range2;
|
|
240
243
|
const [yMin, yMax] = range1;
|
|
241
244
|
const percent = (y - yMin) / (yMax - yMin);
|
|
@@ -243,4 +246,4 @@ class ColorConverter {
|
|
|
243
246
|
return Math.round(ans);
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
|
-
exports.ColorConverter = ColorConverter
|
|
249
|
+
exports.ColorConverter = ColorConverter;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=16.0.0"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.1.
|
|
6
|
+
"version": "2.1.62",
|
|
7
7
|
"description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable. With integrated Philips HUE devices handling.",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"binary-parser": "2.2.1",
|