2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.tradfri.internal;
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;
21 * The {@link TradfriColor} is used for conversion between color formats.
22 * Use the static constructors {@link #TradfriColor(Integer, Integer, Integer)} and
23 * {@link #TradfriColor(HSBType)} for construction.
25 * @author Holger Reichert - Initial contribution
26 * @author Stefan Triller - Use conversions from HSBType
30 public class TradfriColor {
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 };
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.
42 public Integer xyX, xyY;
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.
48 public @Nullable Integer brightness;
51 * Construct from CIE XY values in the tradfri range.
53 * @param xyX x value 0 to 65535
54 * @param xyY y value 0 to 65535
55 * @param brightness brightness from 0 to 254
57 public TradfriColor(Integer xyX, Integer xyY, @Nullable Integer brightness) {
60 if (brightness != null) {
61 if (brightness > 254) {
62 this.brightness = 254;
64 this.brightness = brightness;
70 * Construct from HSBType
72 * @param hsb HSBType from the framework
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);
82 * Obtain the TradfriColor (x/y) as HSBType
84 * @return HSBType representing the x/y Tradfri color
86 public HSBType getHSB() {
87 float x = unnormalize(xyX);
88 float y = unnormalize(xyY);
90 HSBType converted = HSBType.fromXY(x, y);
92 final Integer brightness = this.brightness;
93 if (brightness == null) {
94 throw new IllegalStateException("cannot convert to HSB with brightness=null");
96 return new HSBType(converted.getHue(), converted.getSaturation(), xyBrightnessToPercentType(brightness));
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}
105 * @param percentType the color temperature in percent
107 public TradfriColor(PercentType percentType) {
108 double percent = percentType.doubleValue();
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]));
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]));
128 * Normalize value to the tradfri range.
130 * @param value double in the range 0.0 to 1.0
131 * @return normalized value in the range 0 to 65535
133 private int normalize(double value) {
134 return (int) (value * 65535 + 0.5);
138 * Reverse-normalize value from the tradfri range.
140 * @param value integer in the range 0 to 65535
141 * @return unnormalized value in the range 0.0 to 1.0
143 private float unnormalize(int value) {
144 return (value / 65535.0f);
148 * Calculate the color temperature from given x and y values.
150 * @return {@link PercentType} with color temperature (0 = coolest, 100 = warmest)
152 public PercentType getColorTemperature() {
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)
167 } else if (x > PRESET_X[2]) {
168 // warmer than warmest preset (full color bulbs)
171 return new PercentType((int) Math.round(value * 100.0));
175 * Converts the xyBrightness value to PercentType
177 * @param xyBrightness xy brightness level 0 to 254
178 * @return {@link PercentType} with brightness level (0 = light is off, 1 = lowest, 100 = highest)
180 public static PercentType xyBrightnessToPercentType(int xyBrightness) {
181 if (xyBrightness > 254) {
182 return PercentType.HUNDRED;
183 } else if (xyBrightness < 0) {
184 return PercentType.ZERO;
186 return new PercentType((int) Math.ceil(xyBrightness / 2.54));