]> git.basschouten.com Git - openhab-addons.git/blob
a03caa0b9845f80025692ab7312ca9647d2a2b0f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.tradfri.internal;
14
15 import org.eclipse.jdt.annotation.NonNullByDefault;
16 import org.eclipse.jdt.annotation.Nullable;
17 import org.openhab.core.library.types.HSBType;
18 import org.openhab.core.library.types.PercentType;
19
20 /**
21  * The {@link TradfriColor} is used for conversion between color formats.
22  * Use the static methods {@link TradfriColor#fromCie(int, int, int)} and {@link TradfriColor#fromHSBType(HSBType)} for
23  * construction.
24  *
25  * @author Holger Reichert - Initial contribution
26  * @author Stefan Triller - Use conversions from HSBType
27  *
28  */
29 @NonNullByDefault
30 public class TradfriColor {
31
32     // Tradfri uses the CIE color space (see https://en.wikipedia.org/wiki/CIE_1931_color_space),
33     // which uses x,y-coordinates.
34     // Its own app comes with 3 predefined color temperature settings (0,1,2), which have those values:
35     private static final double[] PRESET_X = new double[] { 24933.0, 30138.0, 33137.0 };
36     private static final double[] PRESET_Y = new double[] { 24691.0, 26909.0, 27211.0 };
37
38     /**
39      * CIE XY color values in the tradfri range 0 to 65535.
40      * May be <code>null</code> if the calculation method does not support this color range.
41      */
42     public Integer xyX, xyY;
43
44     /**
45      * Brightness level in the tradfri range 0 to 254.
46      * May be <code>null</code> if the calculation method does not support this color range.
47      */
48     public @Nullable Integer brightness;
49
50     /**
51      * Construct from CIE XY values in the tradfri range.
52      *
53      * @param xyX x value 0 to 65535
54      * @param xyY y value 0 to 65535
55      * @param xyBrightness brightness from 0 to 254
56      */
57     public TradfriColor(Integer xyX, Integer xyY, @Nullable Integer brightness) {
58         this.xyX = xyX;
59         this.xyY = xyY;
60         if (brightness != null) {
61             if (brightness > 254) {
62                 this.brightness = 254;
63             } else {
64                 this.brightness = brightness;
65             }
66         }
67     }
68
69     /**
70      * Construct from HSBType
71      *
72      * @param hsb HSBType from the framework
73      */
74     public TradfriColor(HSBType hsb) {
75         PercentType[] xyArray = hsb.toXY();
76         this.xyX = normalize(xyArray[0].doubleValue() / 100.0);
77         this.xyY = normalize(xyArray[1].doubleValue() / 100.0);
78         this.brightness = (int) (hsb.getBrightness().floatValue() * 2.54);
79     }
80
81     /**
82      * Obtain the TradfriColor (x/y) as HSBType
83      *
84      * @return HSBType representing the x/y Tradfri color
85      */
86     public HSBType getHSB() {
87         float x = unnormalize(xyX);
88         float y = unnormalize(xyY);
89
90         HSBType converted = HSBType.fromXY(x, y);
91
92         final Integer brightness = this.brightness;
93         if (brightness == null) {
94             throw new IllegalStateException("cannot convert to HSB with brightness=null");
95         }
96         return new HSBType(converted.getHue(), converted.getSaturation(), xyBrightnessToPercentType(brightness));
97     }
98
99     /**
100      * Construct from color temperature in percent.
101      * 0 (coldest) to 100 (warmest).
102      * Note: The resulting {@link TradfriColor} has only the {@link TradfriColor#xyX X} and {@link TradfriColor#xyY y}
103      * values set!
104      *
105      * @param percentType the color temperature in percent
106      */
107     public TradfriColor(PercentType percentType) {
108         double percent = percentType.doubleValue();
109
110         int x, y;
111         if (percent < 50.0) {
112             // we calculate a value that is between preset 0 and 1
113             double p = percent / 50.0;
114             x = (int) Math.round(PRESET_X[0] + p * (PRESET_X[1] - PRESET_X[0]));
115             y = (int) Math.round(PRESET_Y[0] + p * (PRESET_Y[1] - PRESET_Y[0]));
116         } else {
117             // we calculate a value that is between preset 1 and 2
118             double p = (percent - 50) / 50.0;
119             x = (int) Math.round(PRESET_X[1] + p * (PRESET_X[2] - PRESET_X[1]));
120             y = (int) Math.round(PRESET_Y[1] + p * (PRESET_Y[2] - PRESET_Y[1]));
121         }
122
123         this.xyX = x;
124         this.xyY = y;
125     }
126
127     /**
128      * Normalize value to the tradfri range.
129      *
130      * @param value double in the range 0.0 to 1.0
131      * @return normalized value in the range 0 to 65535
132      */
133     private int normalize(double value) {
134         return (int) (value * 65535 + 0.5);
135     }
136
137     /**
138      * Reverse-normalize value from the tradfri range.
139      *
140      * @param value integer in the range 0 to 65535
141      * @return unnormalized value in the range 0.0 to 1.0
142      */
143     private float unnormalize(int value) {
144         return (value / 65535.0f);
145     }
146
147     /**
148      * Calculate the color temperature from given x and y values.
149      *
150      * @return {@link PercentType} with color temperature (0 = coolest, 100 = warmest)
151      */
152     public PercentType getColorTemperature() {
153         double x = xyX;
154         double y = xyY;
155         double value = 0.0;
156         if ((x > PRESET_X[1] && y > PRESET_Y[1]) && (x <= PRESET_X[2] && y <= PRESET_Y[2])) {
157             // is it between preset 1 and 2?
158             value = (x - PRESET_X[1]) / (PRESET_X[2] - PRESET_X[1]) / 2.0 + 0.5;
159         } else if ((x >= PRESET_X[0] && y >= PRESET_Y[0]) && (x <= (PRESET_X[1] + 2.0) && y <= PRESET_Y[1])) {
160             // is it between preset 0 and 1?
161             // hint: in the above line we calculate 2.0 to PRESET_X[1] because
162             // some bulbs send slighty higher x values for this preset (maybe rounding errors?)
163             value = (x - PRESET_X[0]) / (PRESET_X[1] - PRESET_X[0]) / 2.0;
164         } else if (x < PRESET_X[0]) {
165             // cooler than coolest preset (full color bulbs)
166             value = 0.0;
167         } else if (x > PRESET_X[2]) {
168             // warmer than warmest preset (full color bulbs)
169             value = 1.0;
170         }
171         return new PercentType((int) Math.round(value * 100.0));
172     }
173
174     /**
175      * Converts the xyBrightness value to PercentType
176      *
177      * @param xyBrightness xy brightness level 0 to 254
178      * @return {@link PercentType} with brightness level (0 = light is off, 1 = lowest, 100 = highest)
179      */
180     public static PercentType xyBrightnessToPercentType(int xyBrightness) {
181         if (xyBrightness > 254) {
182             return PercentType.HUNDRED;
183         } else if (xyBrightness < 0) {
184             return PercentType.ZERO;
185         }
186         return new PercentType((int) Math.ceil(xyBrightness / 2.54));
187     }
188 }