node-red-contrib-knx-ultimate 2.2.19 → 2.2.21
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 +877 -887
- package/nodes/hue-config.js +25 -5
- package/nodes/knxUltimateHueBattery.html +2 -1
- package/nodes/knxUltimateHueButton.html +2 -1
- package/nodes/knxUltimateHueLight.html +632 -544
- package/nodes/knxUltimateHueLight.js +84 -50
- package/nodes/knxUltimateHueLightSensor.html +3 -1
- package/nodes/knxUltimateHueMotion.html +2 -1
- package/nodes/knxUltimateHueScene.html +2 -1
- package/nodes/knxUltimateHueTapDial.html +2 -1
- package/nodes/knxUltimateHueTemperatureSensor.html +2 -1
- package/nodes/utils/hueColorConverter.js +195 -36
- package/package.json +1 -2
- package/resources/iro@5 +0 -7
|
@@ -35,6 +35,30 @@ module.exports = function (RED) {
|
|
|
35
35
|
node.isGrouped_light = config.hueDevice.split("#")[1] === "grouped_light";
|
|
36
36
|
node.hueDevice = config.hueDevice.split("#")[0];
|
|
37
37
|
node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === "yes");
|
|
38
|
+
config.specifySwitchOnBrightness === undefined ? "yes" : config.specifySwitchOnBrightness;
|
|
39
|
+
// Transform HEX in RGB and stringified json in json oblects.
|
|
40
|
+
if (config.colorAtSwitchOnDayTime.indexOf("#") !== -1) {
|
|
41
|
+
// Transform to rgb.
|
|
42
|
+
try {
|
|
43
|
+
config.colorAtSwitchOnDayTime = hueColorConverter.ColorConverter.hexRgb(config.colorAtSwitchOnDayTime.replace("#", ""));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
config.colorAtSwitchOnDayTime = { red: 255, green: 255, blue: 255 };
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnDayTime);
|
|
49
|
+
}
|
|
50
|
+
// Same thing, but with night color
|
|
51
|
+
if (config.colorAtSwitchOnNightTime.indexOf("#") !== -1) {
|
|
52
|
+
// Transform to rgb.
|
|
53
|
+
try {
|
|
54
|
+
config.colorAtSwitchOnNightTime = hueColorConverter.ColorConverter.hexRgb(config.colorAtSwitchOnNightTime.replace("#", ""));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
config.colorAtSwitchOnNightTime = { red: 12, green: 0, blue: 10 };
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
config.colorAtSwitchOnNightTime = JSON.parse(config.colorAtSwitchOnNightTime);
|
|
60
|
+
}
|
|
61
|
+
|
|
38
62
|
|
|
39
63
|
// Used to call the status update from the config node.
|
|
40
64
|
node.setNodeStatus = ({
|
|
@@ -71,42 +95,42 @@ module.exports = function (RED) {
|
|
|
71
95
|
case config.GALightSwitch:
|
|
72
96
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch));
|
|
73
97
|
if (msg.payload === true) {
|
|
74
|
-
//
|
|
98
|
+
// Check wether the user selected specific color/brightness at switch on.
|
|
75
99
|
let jColorChoosen = null;
|
|
76
|
-
if (node.DayTime === true && (config.specifySwitchOnBrightness ===
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
} catch (error) {
|
|
81
|
-
jColorChoosen = { red: 255, green: 255, blue: 255 };
|
|
82
|
-
}
|
|
83
|
-
} else if (node.DayTime === false && config.enableDayNightLighting === "yes") {
|
|
84
|
-
try {
|
|
85
|
-
jColorChoosen = JSON.parse(config.colorAtSwitchOnNightTime);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
jColorChoosen = { red: 10, green: 10, blue: 10 };
|
|
88
|
-
}
|
|
100
|
+
if (node.DayTime === true && (config.specifySwitchOnBrightness === "yes" || config.specifySwitchOnBrightness === "temperature")) {
|
|
101
|
+
jColorChoosen = config.colorAtSwitchOnDayTime;
|
|
102
|
+
} else if (node.DayTime === false && (config.enableDayNightLighting === "yes" || config.enableDayNightLighting === "temperature")) {
|
|
103
|
+
jColorChoosen = config.colorAtSwitchOnNightTime;
|
|
89
104
|
}
|
|
90
|
-
|
|
105
|
+
|
|
106
|
+
// Now we have a jColorChoosen. Proceed illuminating the light
|
|
107
|
+
if (jColorChoosen !== null && jColorChoosen.kelvin === undefined) {
|
|
108
|
+
// RGB
|
|
91
109
|
let gamut = null;
|
|
92
|
-
if (
|
|
93
|
-
node.currentHUEDevice !== undefined
|
|
94
|
-
&& node.currentHUEDevice.hasOwnProperty("color")
|
|
95
|
-
&& node.currentHUEDevice.color.hasOwnProperty("gamut_type")
|
|
96
|
-
) {
|
|
110
|
+
if (node.currentHUEDevice !== undefined && node.currentHUEDevice.hasOwnProperty("color") && node.currentHUEDevice.color.hasOwnProperty("gamut_type")) {
|
|
97
111
|
gamut = node.currentHUEDevice.color.gamut_type;
|
|
98
112
|
}
|
|
99
113
|
const dretXY = hueColorConverter.ColorConverter.rgbToXy(jColorChoosen.red, jColorChoosen.green, jColorChoosen.blue, gamut);
|
|
100
|
-
const dbright = hueColorConverter.ColorConverter.
|
|
101
|
-
node.currentHUEDevice.dimming.brightness = dbright;
|
|
114
|
+
const dbright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(jColorChoosen.red, jColorChoosen.green, jColorChoosen.blue);
|
|
115
|
+
node.currentHUEDevice.dimming.brightness = Math.round(dbright, 0);
|
|
102
116
|
node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness);
|
|
103
117
|
state = dbright > 0 ? { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } } : { on: { on: false } };
|
|
104
|
-
}
|
|
118
|
+
} if (jColorChoosen !== null && jColorChoosen.kelvin !== undefined) {
|
|
119
|
+
// Kelvin
|
|
120
|
+
const dbright = jColorChoosen.brightness;
|
|
121
|
+
const mirek = hueColorConverter.ColorConverter.kelvinToMirek(jColorChoosen.kelvin);
|
|
122
|
+
node.currentHUEDevice.color_temperature.mirek = mirek;
|
|
123
|
+
node.currentHUEDevice.dimming.brightness = dbright;
|
|
124
|
+
node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness);
|
|
125
|
+
// Kelvin temp
|
|
126
|
+
state = dbright > 0 ? { on: { on: true }, dimming: { brightness: dbright }, color_temperature: { mirek: mirek } } : { on: { on: false } };
|
|
127
|
+
} else if (jColorChoosen === null || jColorChoosen === undefined) {
|
|
105
128
|
state = { on: { on: true } };
|
|
106
129
|
}
|
|
107
130
|
} else {
|
|
108
131
|
state = { on: { on: false } };
|
|
109
132
|
}
|
|
133
|
+
|
|
110
134
|
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, state, node.isGrouped_light === false ? "setLight" : "setGroupedLight");
|
|
111
135
|
node.setNodeStatusHue({
|
|
112
136
|
fill: "green",
|
|
@@ -125,30 +149,37 @@ module.exports = function (RED) {
|
|
|
125
149
|
});
|
|
126
150
|
break;
|
|
127
151
|
case config.GALightKelvin:
|
|
152
|
+
let retMirek;
|
|
128
153
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvin));
|
|
129
|
-
|
|
154
|
+
if (config.dptLightKelvin === "7.600") {
|
|
155
|
+
if (msg.payload > 65535) msg.payload = 65535;
|
|
156
|
+
if (msg.payload < 0) msg.payload = 0;
|
|
157
|
+
retMirek = hueColorConverter.ColorConverter.scale(msg.payload, [0, 65535], [500, 153]);
|
|
158
|
+
} else if (config.dptLightKelvin === "9.002") {
|
|
159
|
+
// Relative temperature in Kelvin. Use HUE scale.
|
|
160
|
+
if (msg.payload > 6535) msg.payload = 6535;
|
|
161
|
+
if (msg.payload < 2000) msg.payload = 2000;
|
|
162
|
+
retMirek = hueColorConverter.ColorConverter.scale(msg.payload, [2000, 6535], [500, 153]);
|
|
163
|
+
}
|
|
130
164
|
state = { color_temperature: { mirek: retMirek } };
|
|
131
165
|
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, state, node.isGrouped_light === false ? "setLight" : "setGroupedLight");
|
|
132
166
|
node.setNodeStatusHue({
|
|
133
167
|
fill: "green",
|
|
134
168
|
shape: "dot",
|
|
135
169
|
text: "KNX->HUE",
|
|
136
|
-
payload:
|
|
170
|
+
payload: msg.payload,
|
|
137
171
|
});
|
|
138
172
|
break;
|
|
139
173
|
case config.GADaylightSensor:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
} else {
|
|
150
|
-
node.DayTime = true;
|
|
151
|
-
}
|
|
174
|
+
node.DayTime = Boolean(dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptDaylightSensor)));
|
|
175
|
+
if (config.invertDayNight !== undefined && config.invertDayNight === true) node.DayTime = !node.DayTime;
|
|
176
|
+
node.setNodeStatusHue({
|
|
177
|
+
fill: "green",
|
|
178
|
+
shape: "dot",
|
|
179
|
+
text: "KNX->HUE Daytime",
|
|
180
|
+
payload: node.DayTime,
|
|
181
|
+
});
|
|
182
|
+
|
|
152
183
|
break;
|
|
153
184
|
case config.GALightHSV:
|
|
154
185
|
if (config.dptLightHSV === "3.007") {
|
|
@@ -209,7 +240,7 @@ module.exports = function (RED) {
|
|
|
209
240
|
gamut = node.currentHUEDevice.color.gamut_type;
|
|
210
241
|
}
|
|
211
242
|
const retXY = hueColorConverter.ColorConverter.rgbToXy(msg.payload.red, msg.payload.green, msg.payload.blue, gamut);
|
|
212
|
-
const bright = hueColorConverter.ColorConverter.
|
|
243
|
+
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(msg.payload.red, msg.payload.green, msg.payload.blue);
|
|
213
244
|
// state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } }
|
|
214
245
|
state = { dimming: { brightness: bright }, color: { xy: retXY } };
|
|
215
246
|
if (node.currentHUEDevice === undefined) {
|
|
@@ -276,7 +307,7 @@ module.exports = function (RED) {
|
|
|
276
307
|
gamut = node.currentHUEDevice.color.gamut_type;
|
|
277
308
|
}
|
|
278
309
|
const retXY = hueColorConverter.ColorConverter.rgbToXy(red, green, blue, gamut);
|
|
279
|
-
const bright = hueColorConverter.ColorConverter.
|
|
310
|
+
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(red, green, blue);
|
|
280
311
|
state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } };
|
|
281
312
|
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, state, node.isGrouped_light === false ? "setLight" : "setGroupedLight");
|
|
282
313
|
} catch (error) { }
|
|
@@ -604,17 +635,20 @@ module.exports = function (RED) {
|
|
|
604
635
|
knxMsgPayload.topic = config.GALightKelvinState;
|
|
605
636
|
knxMsgPayload.dpt = config.dptLightKelvinState;
|
|
606
637
|
if (config.dptLightKelvinState === "7.600") {
|
|
607
|
-
knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(_value, [153, 500], [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
638
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(_value, [153, 500], [65535, 0]);
|
|
639
|
+
} else if (config.dptLightKelvinState === "9.002") {
|
|
640
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(_value, [153, 500], [6535, 2000]);
|
|
641
|
+
}
|
|
642
|
+
// Send to KNX bus
|
|
643
|
+
if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined) {
|
|
644
|
+
node.server.writeQueueAdd({
|
|
645
|
+
grpaddr: knxMsgPayload.topic,
|
|
646
|
+
payload: knxMsgPayload.payload,
|
|
647
|
+
dpt: knxMsgPayload.dpt,
|
|
648
|
+
outputtype: "write",
|
|
649
|
+
nodecallerid: node.id,
|
|
650
|
+
});
|
|
651
|
+
|
|
618
652
|
node.setNodeStatusHue({
|
|
619
653
|
fill: "blue",
|
|
620
654
|
shape: "ring",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
2
|
+
|
|
1
3
|
<script type="text/javascript">
|
|
2
4
|
RED.nodes.registerType('knxUltimateHueLightSensor', {
|
|
3
5
|
category: "KNX Ultimate",
|
|
@@ -228,7 +230,7 @@
|
|
|
228
230
|
|
|
229
231
|
|
|
230
232
|
</script>
|
|
231
|
-
|
|
233
|
+
|
|
232
234
|
|
|
233
235
|
<script type="text/markdown" data-help-name="knxUltimateHueLightSensor">
|
|
234
236
|
This node lets you get the events from your HUE motion device.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
2
|
+
|
|
1
3
|
<script type="text/javascript">
|
|
2
4
|
RED.nodes.registerType('knxUltimateHueMotion', {
|
|
3
5
|
category: "KNX Ultimate",
|
|
@@ -217,7 +219,6 @@
|
|
|
217
219
|
|
|
218
220
|
|
|
219
221
|
</script>
|
|
220
|
-
<script src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
221
222
|
|
|
222
223
|
<script type="text/markdown" data-help-name="knxUltimateHueMotion">
|
|
223
224
|
This node lets you get the events from your HUE motion device.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
2
|
+
|
|
1
3
|
<script type="text/javascript">
|
|
2
4
|
RED.nodes.registerType('knxUltimateHueScene', {
|
|
3
5
|
category: "KNX Ultimate",
|
|
@@ -283,7 +285,6 @@
|
|
|
283
285
|
|
|
284
286
|
|
|
285
287
|
</script>
|
|
286
|
-
<script src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
287
288
|
|
|
288
289
|
<script type="text/markdown" data-help-name="knxUltimateHueScene">
|
|
289
290
|
This node lets you recall a HUE scene, via KNX.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
2
|
+
|
|
1
3
|
<script type="text/javascript">
|
|
2
4
|
RED.nodes.registerType('knxUltimateHueTapDial', {
|
|
3
5
|
category: "KNX Ultimate",
|
|
@@ -219,7 +221,6 @@
|
|
|
219
221
|
|
|
220
222
|
|
|
221
223
|
</script>
|
|
222
|
-
<script src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
223
224
|
|
|
224
225
|
<script type="text/markdown" data-help-name="knxUltimateHueTapDial">
|
|
225
226
|
This node lets you get the events from your HUE rotary device, for example the Tap Dial.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
2
|
+
|
|
1
3
|
<script type="text/javascript">
|
|
2
4
|
RED.nodes.registerType('knxUltimateHueTemperatureSensor', {
|
|
3
5
|
category: "KNX Ultimate",
|
|
@@ -227,7 +229,6 @@
|
|
|
227
229
|
|
|
228
230
|
|
|
229
231
|
</script>
|
|
230
|
-
<script src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
|
|
231
232
|
|
|
232
233
|
<script type="text/markdown" data-help-name="knxUltimateHueTemperatureSensor" This node lets you get the events from
|
|
233
234
|
your HUE temperature device. Here you can get the HUE temperature events, that represents a celsius value, evetytime
|
|
@@ -1,7 +1,165 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Part of this code, thanks to https://github.com/Shnoo/js-CIE-1931-rgb-color-converter
|
|
3
2
|
class ColorConverter {
|
|
3
|
+
// static getBrightnessFromRGBOrHex(red, green, blue) {
|
|
4
|
+
// const hsv = convert.rgb.hsv(red, green, blue);
|
|
5
|
+
// const brightness = hsv[2];
|
|
6
|
+
// return brightness;
|
|
7
|
+
// }
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
static getBrightnessFromRGBOrHex(Rint, Gint, Bint) { // takes sRGB channels as 8 bit integers
|
|
11
|
+
|
|
12
|
+
var Rlin = (Rint / 255.0) ** 2.218; // Convert int to decimal 0-1 and linearize
|
|
13
|
+
var Glin = (Gint / 255.0) ** 2.218; // ** is the exponentiation operator, older JS needs Math.pow() instead
|
|
14
|
+
var Blin = (Bint / 255.0) ** 2.218; // 2.218 Gamma for sRGB linearization. 2.218 sets unity with the piecewise sRGB at #777 .... 2.2 or 2.223 could be used instead
|
|
15
|
+
|
|
16
|
+
var Ylum = Rlin * 0.2126 + Glin * 0.7156 + Blin * 0.0722; // convert to Luminance Y
|
|
17
|
+
|
|
18
|
+
return Math.pow(Ylum, 0.43) * 100; // Convert to lightness (0 to 100)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static convert_1_255_ToPercentage(number) {
|
|
22
|
+
const percentage = (number / 255) * 100;
|
|
23
|
+
return percentage;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static kelvinToMirek(_kelvin) {
|
|
27
|
+
return Math.round(1000000 / _kelvin, 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static mirekToKelvin(_mirek) {
|
|
31
|
+
return Math.round(1000000 / _mirek, 0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Linear interpolation of input y given starting and ending ranges
|
|
35
|
+
static scale(y, range1 = [0, 100], range2 = [0, 255]) {
|
|
36
|
+
const [xMin, xMax] = range2;
|
|
37
|
+
const [yMin, yMax] = range1;
|
|
38
|
+
const percent = (y - yMin) / (yMax - yMin);
|
|
39
|
+
const ans = percent * (xMax - xMin) + xMin;
|
|
40
|
+
return Math.round(ans);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Thanks to: https://github.com/sindresorhus/rgb-hex
|
|
44
|
+
static rgbHex(red, green, blue, alpha) {
|
|
45
|
+
const toHex = (red, green, blue, alpha) => ((blue | green << 8 | red << 16) | 1 << 24).toString(16).slice(1) + alpha;
|
|
46
|
+
const parseCssRgbString = (input) => {
|
|
47
|
+
const parts = input.replace(/rgba?\(([^)]+)\)/, '$1').split(/[,\s/]+/).filter(Boolean);
|
|
48
|
+
if (parts.length < 3) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parseValue = (value, max) => {
|
|
53
|
+
value = value.trim();
|
|
54
|
+
|
|
55
|
+
if (value.endsWith('%')) {
|
|
56
|
+
return Math.min(Number.parseFloat(value) * max / 100, max);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Math.min(Number.parseFloat(value), max);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const red = parseValue(parts[0], 255);
|
|
63
|
+
const green = parseValue(parts[1], 255);
|
|
64
|
+
const blue = parseValue(parts[2], 255);
|
|
65
|
+
let alpha;
|
|
66
|
+
|
|
67
|
+
if (parts.length === 4) {
|
|
68
|
+
alpha = parseValue(parts[3], 1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return [red, green, blue, alpha];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let isPercent = (red + (alpha || '')).toString().includes('%');
|
|
75
|
+
|
|
76
|
+
if (typeof red === 'string' && !green) { // Single string parameter.
|
|
77
|
+
const parsed = parseCssRgbString(red);
|
|
78
|
+
if (!parsed) {
|
|
79
|
+
throw new TypeError('Invalid or unsupported color format.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
isPercent = false;
|
|
83
|
+
[red, green, blue, alpha] = parsed;
|
|
84
|
+
} else if (alpha !== undefined) {
|
|
85
|
+
alpha = Number.parseFloat(alpha);
|
|
86
|
+
}
|
|
4
87
|
|
|
88
|
+
if (typeof red !== 'number'
|
|
89
|
+
|| typeof green !== 'number'
|
|
90
|
+
|| typeof blue !== 'number'
|
|
91
|
+
|| red > 255
|
|
92
|
+
|| green > 255
|
|
93
|
+
|| blue > 255
|
|
94
|
+
) {
|
|
95
|
+
throw new TypeError('Expected three numbers below 256');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof alpha === 'number') {
|
|
99
|
+
if (!isPercent && alpha >= 0 && alpha <= 1) {
|
|
100
|
+
alpha = Math.round(255 * alpha);
|
|
101
|
+
} else if (isPercent && alpha >= 0 && alpha <= 100) {
|
|
102
|
+
alpha = Math.round(255 * alpha / 100);
|
|
103
|
+
} else {
|
|
104
|
+
throw new TypeError(`Expected alpha value (${alpha}) as a fraction or percentage`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
alpha = (alpha | 1 << 8).toString(16).slice(1); // eslint-disable-line no-mixed-operators
|
|
108
|
+
} else {
|
|
109
|
+
alpha = '';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return toHex(red, green, blue, alpha);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Thanks to: https://github.com/sindresorhus/hex-rgb
|
|
116
|
+
static hexRgb(hex, options = {}) {
|
|
117
|
+
const hexCharacters = 'a-f\\d';
|
|
118
|
+
const match3or4Hex = `#?[${hexCharacters}]{3}[${hexCharacters}]?`;
|
|
119
|
+
const match6or8Hex = `#?[${hexCharacters}]{6}([${hexCharacters}]{2})?`;
|
|
120
|
+
const nonHexChars = new RegExp(`[^#${hexCharacters}]`, 'gi');
|
|
121
|
+
const validHexSize = new RegExp(`^${match3or4Hex}$|^${match6or8Hex}$`, 'i');
|
|
122
|
+
|
|
123
|
+
if (typeof hex !== 'string' || nonHexChars.test(hex) || !validHexSize.test(hex)) {
|
|
124
|
+
throw new TypeError('Expected a valid hex string');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
hex = hex.replace(/^#/, '');
|
|
128
|
+
let alphaFromHex = 1;
|
|
129
|
+
|
|
130
|
+
if (hex.length === 8) {
|
|
131
|
+
alphaFromHex = Number.parseInt(hex.slice(6, 8), 16) / 255;
|
|
132
|
+
hex = hex.slice(0, 6);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (hex.length === 4) {
|
|
136
|
+
alphaFromHex = Number.parseInt(hex.slice(3, 4).repeat(2), 16) / 255;
|
|
137
|
+
hex = hex.slice(0, 3);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (hex.length === 3) {
|
|
141
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const number = Number.parseInt(hex, 16);
|
|
145
|
+
const red = number >> 16;
|
|
146
|
+
const green = (number >> 8) & 255;
|
|
147
|
+
const blue = number & 255;
|
|
148
|
+
const alpha = typeof options.alpha === 'number' ? options.alpha : alphaFromHex;
|
|
149
|
+
|
|
150
|
+
if (options.format === 'array') {
|
|
151
|
+
return [red, green, blue, alpha];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (options.format === 'css') {
|
|
155
|
+
const alphaString = alpha === 1 ? '' : ` / ${Number((alpha * 100).toFixed(2))}%`;
|
|
156
|
+
return `rgb(${red} ${green} ${blue}${alphaString})`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
red, green, blue, alpha,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
5
163
|
|
|
6
164
|
static getGamutRanges() {
|
|
7
165
|
const gamutA = {
|
|
@@ -33,31 +191,52 @@ class ColorConverter {
|
|
|
33
191
|
};
|
|
34
192
|
}
|
|
35
193
|
|
|
36
|
-
static getLightColorGamutRange(
|
|
194
|
+
static getLightColorGamutRange(modelId = null) {
|
|
37
195
|
const ranges = ColorConverter.getGamutRanges();
|
|
38
196
|
const { gamutA } = ranges;
|
|
39
197
|
const { gamutB } = ranges;
|
|
40
198
|
const { gamutC } = ranges;
|
|
41
199
|
|
|
42
200
|
const philipsModels = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
201
|
+
LST001: gamutA,
|
|
202
|
+
LLC010: gamutA,
|
|
203
|
+
LLC011: gamutA,
|
|
204
|
+
LLC012: gamutA,
|
|
205
|
+
LLC006: gamutA,
|
|
206
|
+
LLC005: gamutA,
|
|
207
|
+
LLC007: gamutA,
|
|
208
|
+
LLC014: gamutA,
|
|
209
|
+
LLC013: gamutA,
|
|
210
|
+
|
|
211
|
+
LCT001: gamutB,
|
|
212
|
+
LCT007: gamutB,
|
|
213
|
+
LCT002: gamutB,
|
|
214
|
+
LCT003: gamutB,
|
|
215
|
+
LLM001: gamutB,
|
|
216
|
+
|
|
217
|
+
LCT010: gamutC,
|
|
218
|
+
LCT014: gamutC,
|
|
219
|
+
LCT015: gamutC,
|
|
220
|
+
LCT016: gamutC,
|
|
221
|
+
LCT011: gamutC,
|
|
222
|
+
LLC020: gamutC,
|
|
223
|
+
LST002: gamutC,
|
|
224
|
+
LCT012: gamutC,
|
|
46
225
|
};
|
|
47
226
|
|
|
48
|
-
if (philipsModels[
|
|
49
|
-
return philipsModels[
|
|
227
|
+
if (philipsModels[modelId]) {
|
|
228
|
+
return philipsModels[modelId];
|
|
50
229
|
}
|
|
51
230
|
|
|
52
231
|
return ranges.default;
|
|
53
232
|
}
|
|
54
233
|
|
|
55
|
-
static rgbToXy(red, green, blue,
|
|
234
|
+
static rgbToXy(red, green, blue, modelId = null) {
|
|
56
235
|
function getGammaCorrectedValue(value) {
|
|
57
236
|
return (value > 0.04045) ? ((value + 0.055) / (1.0 + 0.055)) ** 2.4 : (value / 12.92);
|
|
58
237
|
}
|
|
59
238
|
|
|
60
|
-
const colorGamut = ColorConverter.getLightColorGamutRange(
|
|
239
|
+
const colorGamut = ColorConverter.getLightColorGamutRange(modelId);
|
|
61
240
|
|
|
62
241
|
red = parseFloat(red / 255);
|
|
63
242
|
green = parseFloat(green / 255);
|
|
@@ -83,17 +262,6 @@ class ColorConverter {
|
|
|
83
262
|
return xy;
|
|
84
263
|
}
|
|
85
264
|
|
|
86
|
-
static getBrightnessFromRGB(red, green, blue) {
|
|
87
|
-
const hsv = convert.rgb.hsv(red, green, blue);
|
|
88
|
-
const brightness = hsv[2];
|
|
89
|
-
return brightness;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
static convert_1_255_ToPercentage(number) {
|
|
93
|
-
const percentage = (number / 255) * 100;
|
|
94
|
-
return percentage;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
265
|
static xyIsInGamutRange(xy, gamut) {
|
|
98
266
|
gamut = gamut || ColorConverter.getGamutRanges().gamutC;
|
|
99
267
|
if (Array.isArray(xy)) {
|
|
@@ -211,9 +379,9 @@ class ColorConverter {
|
|
|
211
379
|
const Y = bri / 255;
|
|
212
380
|
const X = (Y / y) * x;
|
|
213
381
|
const Z = (Y / y) * z;
|
|
214
|
-
let r = X * 1.
|
|
215
|
-
let g = -X * 0.
|
|
216
|
-
let b = X * 0.
|
|
382
|
+
let r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
|
|
383
|
+
let g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
|
|
384
|
+
let b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
|
|
217
385
|
|
|
218
386
|
r = getReversedGammaCorrectedValue(r);
|
|
219
387
|
g = getReversedGammaCorrectedValue(g);
|
|
@@ -233,19 +401,10 @@ class ColorConverter {
|
|
|
233
401
|
}
|
|
234
402
|
|
|
235
403
|
return {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
404
|
+
r: Math.floor(r * 255),
|
|
405
|
+
g: Math.floor(g * 255),
|
|
406
|
+
b: Math.floor(b * 255),
|
|
239
407
|
};
|
|
240
408
|
}
|
|
241
|
-
|
|
242
|
-
// Linear interpolation of input y given starting and ending ranges
|
|
243
|
-
static scale(y, range1 = [0, 100], range2 = [0, 255]) {
|
|
244
|
-
const [xMin, xMax] = range2;
|
|
245
|
-
const [yMin, yMax] = range1;
|
|
246
|
-
const percent = (y - yMin) / (yMax - yMin);
|
|
247
|
-
const ans = percent * (xMax - xMin) + xMin;
|
|
248
|
-
return Math.round(ans);
|
|
249
|
-
}
|
|
250
409
|
}
|
|
251
410
|
exports.ColorConverter = ColorConverter;
|
package/package.json
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=16.0.0"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.2.
|
|
6
|
+
"version": "2.2.21",
|
|
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 control.",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"binary-parser": "2.2.1",
|
|
10
|
-
"color-convert": "2.0.1",
|
|
11
10
|
"crypto-js": "4.2.0",
|
|
12
11
|
"dns-sync": "0.2.1",
|
|
13
12
|
"eventsource": "2.0.2",
|