diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/LightModel.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/LightModel.java
index 60f17b35f0d..ebd9933ab55 100644
--- a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/LightModel.java
+++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/LightModel.java
@@ -15,6 +15,7 @@
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Objects;
+import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -63,7 +64,7 @@
* Also set {@link #rgbDataType} to the chosen RGB data type RGB, RGBW, RGBCW etc.
* And optionally set the following configuration parameters:
*
- * - Optionally override {@link #minimumOnBrightness} to a minimum brightness percent in the range [0.1..10.0]
+ *
- Optionally override {@link #minimumOnBrightness} to a minimum brightness percent in the range [0.0..10.0]
* percent, to consider as being "ON". The default is 1 percent.
* - Optionally override {@link #mirekControlWarmest} to a 'warmest' white color temperature in the range
* [{@link #mirekControlCoolest}..1000.0] Mirek/Mired. The default is 500 Mirek/Mired.
@@ -237,9 +238,30 @@ public static enum RgbDataType {
/** supports 4-element RGB with white channel */
RGB_W,
/** supports 5-element RGB with cold and warm white channels */
- RGB_C_W
+ RGB_C_W,
+ /** as RGB_W but ignores brightness (i.e. only HS parts of HSBType) */
+ RGB_W_NO_BRIGHTNESS,
+ /** as RGB_C_W but ignores brightness (i.e. only HS parts of HSBType) */
+ RGB_C_W_NO_BRIGHTNESS
}
+ /**
+ * Set of RGB data types that do not use the brightness part of the HSBType state.
+ */
+ private static final Set NO_BRIGHTNESS_TYPES = Set.of(RgbDataType.RGB_NO_BRIGHTNESS,
+ RgbDataType.RGB_W_NO_BRIGHTNESS, RgbDataType.RGB_C_W_NO_BRIGHTNESS);
+
+ /**
+ * Set of RGB data types that use a white channel.
+ */
+ private static final Set RGB_W_TYPES = Set.of(RgbDataType.RGB_W, RgbDataType.RGB_W_NO_BRIGHTNESS);
+
+ /**
+ * Set of RGB data types that use cold and warm white channels.
+ */
+ private static final Set RGB_C_W_TYPES = Set.of(RgbDataType.RGB_C_W,
+ RgbDataType.RGB_C_W_NO_BRIGHTNESS);
+
/**
* Enum for the LED operating mode
*
@@ -518,9 +540,9 @@ public synchronized void configSetLightCapabilities(LightCapabilities lightCapab
* @throws IllegalArgumentException if the minimumBrightness parameter is out of range.
*/
public synchronized void configSetMinimumOnBrightness(double minimumOnBrightness) throws IllegalArgumentException {
- if (minimumOnBrightness < 0.1 || minimumOnBrightness > 10.0) {
+ if (minimumOnBrightness < 0.0 || minimumOnBrightness > 10.0) {
throw new IllegalArgumentException(
- "Minimum brightness '%.1f' out of range [0.1..10.0]".formatted(minimumOnBrightness));
+ "Minimum brightness '%.1f' out of range [0.0..10.0]".formatted(minimumOnBrightness));
}
this.minimumOnBrightness = minimumOnBrightness;
}
@@ -723,7 +745,7 @@ public synchronized double getMirek() {
*/
public synchronized @Nullable OnOffType getOnOff(boolean forceChannelVisible) {
return (!lightCapabilities.supportsColor() && !lightCapabilities.supportsBrightness()) || forceChannelVisible
- ? OnOffType.from(cachedHSB.getBrightness().doubleValue() >= minimumOnBrightness)
+ ? OnOffType.from(cachedHSB.getBrightness().doubleValue() > minimumOnBrightness)
: null;
}
@@ -734,20 +756,20 @@ public synchronized double getMirek() {
* follows:
*
*
- * - 'RGB_NO_BRIGHTNESS': The return result does not depend on the current brightness. In other words the values
- * only relate to the 'HS' part of the {@link HSBType} state. Note: this means that in this case a round trip of
- * setRGBx() followed by getRGBx() will NOT necessarily contain identical values, although the RGB ratios will
- * certainly be the same.
+ * - 'RGB_NO_BRIGHTNESS', 'RGB_W_NO_BRIGHTNESS', 'RGB_C_W_NO_BRIGHTNESS': The return result does not depend on
+ * the current brightness. In other words the values only relate to the 'HS' part of the {@link HSBType} state.
+ * Note: this means that in this case a round trip of setRGBx() followed by getRGBx() will NOT necessarily contain
+ * identical values, although the RGB ratios will certainly be the same.
*
* - All other values of {@link #rgbDataType}: The return result depends on the current brightness. In other
* words the values relate to all the 'HSB' parts of the {@link HSBType} state.
- *
*
* @return double[] representing the RGB(C)(W) components in range [0..255.0]
* @throws IllegalStateException if the RGB data type is not compatible with the current LED operating mode.
*/
public synchronized double[] getRGBx() throws IllegalStateException {
- HSBType hsb = RgbDataType.RGB_NO_BRIGHTNESS == rgbDataType
+ HSBType hsb = NO_BRIGHTNESS_TYPES.contains(rgbDataType)
? new HSBType(cachedHSB.getHue(), cachedHSB.getSaturation(), PercentType.HUNDRED)
: cachedHSB;
@@ -758,8 +780,8 @@ public synchronized double[] getRGBx() throws IllegalStateException {
/*
* If the light has a single white led then its value is determined by the brightness only.
*/
- if (RgbDataType.RGB_W == rgbDataType) {
- double w = cachedHSB.getBrightness().doubleValue() * 255.0 / 100.0;
+ if (RGB_W_TYPES.contains(rgbDataType)) {
+ double w = hsb.getBrightness().doubleValue() * 255.0 / 100.0;
return new double[] { 0.0, 0.0, 0.0, w };
}
@@ -767,7 +789,7 @@ public synchronized double[] getRGBx() throws IllegalStateException {
* If the light has a warm and a cool white led, the mix of white values are determined
* by the brightness and the color temperature.
*/
- if (RgbDataType.RGB_C_W == rgbDataType) {
+ if (RGB_C_W_TYPES.contains(rgbDataType)) {
double denominator = warmWhiteLed.getMirek() - coolWhiteLed.getMirek();
double ratio;
if (denominator > 0 && !Double.isNaN(cachedMirek)) {
@@ -775,7 +797,7 @@ public synchronized double[] getRGBx() throws IllegalStateException {
} else {
ratio = 0.5;
}
- double bri = cachedHSB.getBrightness().doubleValue() * 255.0 / 100.0;
+ double bri = hsb.getBrightness().doubleValue() * 255.0 / 100.0;
double cool = bri * ratio;
double warm = bri - cool;
return new double[] { 0.0, 0.0, 0.0, cool, warm };
@@ -794,9 +816,9 @@ public synchronized double[] getRGBx() throws IllegalStateException {
*/
PercentType[] rgbP = ColorUtil.hsbToRgbPercent(hsb);
double[] rgb = Arrays.stream(rgbP).mapToDouble(p -> p.doubleValue() * 255.0 / 100.0).toArray();
- if (RgbDataType.RGB_W == rgbDataType) {
+ if (RGB_W_TYPES.contains(rgbDataType)) {
return new double[] { rgb[0], rgb[1], rgb[2], 0 };
- } else if (RgbDataType.RGB_C_W == rgbDataType) {
+ } else if (RGB_C_W_TYPES.contains(rgbDataType)) {
return new double[] { rgb[0], rgb[1], rgb[2], 0, 0 };
}
return rgb;
@@ -806,7 +828,7 @@ public synchronized double[] getRGBx() throws IllegalStateException {
* In combined mode the RGB and white values are all determined by the HSB values.
*/
if (LedOperatingMode.COMBINED == ledOperatingMode) {
- if (RgbDataType.RGB_C_W == rgbDataType) {
+ if (RGB_C_W_TYPES.contains(rgbDataType)) {
/*
* RGBCW - convert HSB to RGB, normalize it, then convert to RGBCW, then scale to [0..255]
*/
@@ -815,7 +837,7 @@ public synchronized double[] getRGBx() throws IllegalStateException {
double[] rgbcw = RgbcwMath.rgb2rgbcw(rgb, coolWhiteLed.getProfile(), warmWhiteLed.getProfile());
rgbcw = Arrays.stream(rgbcw).map(d -> Math.round(d * 255 * 10) / 10).toArray(); // // round to 1
return rgbcw;
- } else if (RgbDataType.RGB_W == rgbDataType) {
+ } else if (RGB_W_TYPES.contains(rgbDataType)) {
/*
* RGBW - convert HSB to RGBW, then scale to [0..255]
*/
@@ -1031,15 +1053,15 @@ public synchronized void setOnOff(boolean on) {
* on the value of {@link #rgbDataType} the brightness may or may not change as follows:
*
*
- * - 'RGB_NO_BRIGHTNESS' both [255,0,0] and [127.5,0,0] change the color to RED without a change in brightness.
- * In other words the values only relate to the 'HS' part of the {@link HSBType} state. Note: this means that in
- * this case a round trip of 'setRGBx()' followed by 'getRGBx()' will NOT necessarily contain identical values,
- * although the RGB ratios will certainly be the same.
+ * - 'RGB_NO_BRIGHTNESS', 'RGB_W_NO_BRIGHTNESS', 'RGB_C_W_NO_BRIGHTNESS': The set value does not affect the
+ * current brightness. In other words the values only relate to the 'HS' part of the {@link HSBType} state. Note:
+ * this means that in this case a round trip of 'setRGBx()' followed by 'getRGBx()' will NOT necessarily contain
+ * identical values, although the RGB ratios will certainly be the same.
*
* - All other values of {@link #rgbDataType}: both [255,0,0] and [127.5,0,0] change the color to RED and the
* former changes the brightness to 100 percent, whereas the latter changes it to 50 percent. In other words the
* values relate to all the 'HSB' parts of the {@link HSBType} state.
- *
*
* @param rgbxParameter an array of double representing RGB or RGBW values in range [0.0..255.0]
* @throws IllegalArgumentException if the array length is not 3, 4, or 5 depending on the light's capabilities,
@@ -1049,8 +1071,8 @@ public synchronized void setRGBx(double[] rgbxParameter) throws IllegalArgumentE
if (rgbxParameter.length > 5) {
throw new IllegalArgumentException("Too many arguments in RGBx array");
}
- if (rgbxParameter.length < 3 || (RgbDataType.RGB_W == rgbDataType && rgbxParameter.length < 4)
- || (RgbDataType.RGB_C_W == rgbDataType && rgbxParameter.length < 5)) {
+ if (rgbxParameter.length < 3 || (RGB_W_TYPES.contains(rgbDataType) && rgbxParameter.length < 4)
+ || (RGB_C_W_TYPES.contains(rgbDataType) && rgbxParameter.length < 5)) {
throw new IllegalArgumentException("Too few arguments in RGBx array");
}
if (rgbxParameter.length == 3 && ledOperatingMode != LedOperatingMode.RGB_ONLY) {
@@ -1064,10 +1086,11 @@ public synchronized void setRGBx(double[] rgbxParameter) throws IllegalArgumentE
}
HSBType hsb;
+ Double mirek;
+ PercentType oldBri = cachedHSB.getBrightness();
switch (ledOperatingMode) {
case WHITE_ONLY:
double white;
- double mirek;
if (rgbxParameter.length == 5) {
/*
* We have both a C and a W channel so we create a pure white whose brightness
@@ -1114,7 +1137,7 @@ public synchronized void setRGBx(double[] rgbxParameter) throws IllegalArgumentE
case COMBINED:
double[] rgbx;
- if (RgbDataType.RGB_C_W == rgbDataType) {
+ if (RGB_C_W_TYPES.contains(rgbDataType)) {
// RGBCW - normalize, convert to RGB, then scale back to [0..255]
rgbx = Arrays.stream(rgbxParameter).map(d -> d / 255.0).toArray();
rgbx = RgbcwMath.rgbcw2rgb(rgbx, coolWhiteLed.getProfile(), warmWhiteLed.getProfile());
@@ -1127,17 +1150,20 @@ public synchronized void setRGBx(double[] rgbxParameter) throws IllegalArgumentE
hsb = ColorUtil.rgbToHsb(Arrays.stream(rgbx).map(d -> d * 100.0 / 255.0)
.mapToObj(d -> zPercentTypeFrom(d)).toArray(PercentType[]::new));
- if (RgbDataType.RGB_NO_BRIGHTNESS == rgbDataType) {
- hsb = new HSBType(hsb.getHue(), hsb.getSaturation(), cachedHSB.getBrightness());
- }
+ mirek = zMirekFrom(hsb);
break;
default:
return; // safe coding but will never happen
}
- cachedHSB = hsb;
- cachedMirek = zMirekFrom(hsb);
+ if (NO_BRIGHTNESS_TYPES.contains(rgbDataType)) {
+ cachedHSB = new HSBType(hsb.getHue(), hsb.getSaturation(), oldBri);
+ } else {
+ cachedHSB = hsb;
+ zHandleBrightness(hsb.getBrightness()); // refresh cached brightness and on/off state
+ }
+ cachedMirek = mirek;
}
/**
@@ -1209,12 +1235,12 @@ public synchronized LightModel copy() {
* @param brightness the brightness {@link PercentType} to set.
*/
private void zHandleBrightness(PercentType brightness) {
- if (brightness.doubleValue() >= minimumOnBrightness) {
+ if (brightness.doubleValue() > minimumOnBrightness) {
cachedBrightness = brightness;
cachedHSB = new HSBType(cachedHSB.getHue(), cachedHSB.getSaturation(), brightness);
cachedOnOff = OnOffType.ON;
} else {
- if (OnOffType.ON == cachedOnOff && cachedHSB.getBrightness().doubleValue() >= minimumOnBrightness) {
+ if (OnOffType.ON == cachedOnOff && cachedHSB.getBrightness().doubleValue() > minimumOnBrightness) {
cachedBrightness = cachedHSB.getBrightness(); // cache the last 'ON' state brightness
}
cachedHSB = new HSBType(cachedHSB.getHue(), cachedHSB.getSaturation(), PercentType.ZERO);
diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/LightModelTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/LightModelTest.java
index 0db94695b10..a05455576fd 100644
--- a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/LightModelTest.java
+++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/LightModelTest.java
@@ -120,8 +120,8 @@ public void testColorWithoutColorTemperature() {
assertEquals(PercentType.HUNDRED, lsm.getBrightness(true));
assertNull(lsm.getColorTemperature());
assertNull(lsm.getColorTemperaturePercent());
- assertEquals(UnDefType.UNDEF, lsm.toNonNull(lsm.getColorTemperature()));
- assertEquals(UnDefType.UNDEF, lsm.toNonNull(lsm.getColorTemperaturePercent()));
+ assertEquals(UnDefType.UNDEF, LightModel.toNonNull(lsm.getColorTemperature()));
+ assertEquals(UnDefType.UNDEF, LightModel.toNonNull(lsm.getColorTemperaturePercent()));
lsm.handleCommand(PercentType.ZERO);
assertEquals(PercentType.ZERO, lsm.getBrightness(true));
@@ -139,7 +139,7 @@ public void testBrightnessAndColorTemperature() {
assertNull(lsm.getColor());
assertEquals(PercentType.HUNDRED, lsm.getBrightness());
assertEquals(OnOffType.ON, lsm.getOnOff(true));
- assertEquals(UnDefType.UNDEF, lsm.toNonNull(lsm.getColor()));
+ assertEquals(UnDefType.UNDEF, LightModel.toNonNull(lsm.getColor()));
lsm.handleCommand(QuantityType.valueOf(500, Units.MIRED));
assertEquals(OnOffType.ON, lsm.getOnOff(true));
@@ -162,7 +162,7 @@ public void testBrightnessOnly() {
assertNull(lsm.getColor());
assertEquals(PercentType.HUNDRED, lsm.getBrightness());
assertEquals(OnOffType.ON, lsm.getOnOff(true));
- assertEquals(UnDefType.UNDEF, lsm.toNonNull(lsm.getColor()));
+ assertEquals(UnDefType.UNDEF, LightModel.toNonNull(lsm.getColor()));
lsm.handleCommand(QuantityType.valueOf(500, Units.MIRED));
assertEquals(OnOffType.ON, lsm.getOnOff(true));
@@ -185,8 +185,8 @@ public void testOnOffOnly() {
assertNull(lsm.getColor());
assertNull(lsm.getBrightness());
assertEquals(OnOffType.ON, lsm.getOnOff());
- assertEquals(UnDefType.UNDEF, lsm.toNonNull(lsm.getColor()));
- assertEquals(UnDefType.UNDEF, lsm.toNonNull(lsm.getBrightness()));
+ assertEquals(UnDefType.UNDEF, LightModel.toNonNull(lsm.getColor()));
+ assertEquals(UnDefType.UNDEF, LightModel.toNonNull(lsm.getBrightness()));
lsm.handleCommand(QuantityType.valueOf(500, Units.MIRED));
assertEquals(OnOffType.ON, lsm.getOnOff());
@@ -286,8 +286,8 @@ public void testParameterSetters() {
@Test
public void testParameterSettersBad() {
LightModel lsm = new LightModel();
- assertThrows(IllegalArgumentException.class, () -> lsm.configSetMinimumOnBrightness(0.0));
- assertThrows(IllegalArgumentException.class, () -> lsm.configSetMinimumOnBrightness(11.0));
+ assertThrows(IllegalArgumentException.class, () -> lsm.configSetMinimumOnBrightness(-0.1));
+ assertThrows(IllegalArgumentException.class, () -> lsm.configSetMinimumOnBrightness(10.1));
assertThrows(IllegalArgumentException.class, () -> lsm.configSetMirekControlWarmest(153.0));
assertThrows(IllegalArgumentException.class, () -> lsm.configSetMirekControlWarmest(99.0));
@@ -301,6 +301,13 @@ public void testParameterSettersBad() {
assertThrows(IllegalArgumentException.class, () -> lsm.configSetIncreaseDecreaseStep(51.0));
}
+ @Test
+ public void testParameterSettersGood() {
+ LightModel lsm = new LightModel();
+ assertDoesNotThrow(() -> lsm.configSetMinimumOnBrightness(-0.0));
+ assertDoesNotThrow(() -> lsm.configSetMinimumOnBrightness(10.0));
+ }
+
@Test
public void testCommandsBad() {
LightModel lsm = new LightModel();
@@ -314,11 +321,11 @@ public void testCommandsBad() {
@Test
public void testComplexConstructorBad() {
assertThrows(IllegalArgumentException.class,
- () -> new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE, RgbDataType.DEFAULT, 0.0, null,
+ () -> new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE, RgbDataType.DEFAULT, -0.1, null,
null, null, null, null));
assertThrows(IllegalArgumentException.class,
- () -> new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE, RgbDataType.DEFAULT, 11.0, null,
+ () -> new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE, RgbDataType.DEFAULT, 10.1, null,
null, null, null, null));
assertThrows(IllegalArgumentException.class,
@@ -389,6 +396,15 @@ public void testRgbIgnoreBrightness() {
assertEquals(0.0, rgb[0], 1);
assertEquals(127.5, rgb[1], 1);
assertEquals(255.0, rgb[2], 1);
+
+ // confirm that the brightness is still 50%
+ PercentType brightness = lsm.getBrightness(true);
+ assertNotNull(brightness);
+ assertEquals(new PercentType(50), brightness);
+
+ HSBType color = lsm.getColor();
+ assertNotNull(color);
+ assertEquals(50.0, color.getBrightness().doubleValue(), 1);
}
@Test
@@ -914,4 +930,240 @@ public void testSwitchLedOperationMode() {
assertEquals(0.0, rgbx[2], 0.01);
assertEquals(255.0, rgbx[3] + rgbx[4], 0.01);
}
+
+ @Test
+ public void testRgbwIgnoreBrightness() {
+ LightModel lsm = new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ RgbDataType.RGB_W_NO_BRIGHTNESS);
+ assertTrue(lsm.configGetLightCapabilities().supportsColor());
+ assertTrue(lsm.configGetLightCapabilities().supportsBrightness());
+ assertTrue(lsm.configGetLightCapabilities().supportsColorTemperature());
+
+ lsm.handleCommand(HSBType.RED);
+ assertEquals(HSBType.RED, lsm.getColor());
+ assertEquals(PercentType.HUNDRED, lsm.getBrightness(true));
+ assertEquals(OnOffType.ON, lsm.getOnOff(true));
+
+ double[] full = lsm.getRGBx();
+ assertEquals(4, full.length);
+ assertEquals(255.0, full[0], 1);
+ assertEquals(0.0, full[1], 1);
+ assertEquals(0.0, full[2], 1);
+ assertEquals(0.0, full[3], 1);
+
+ lsm.handleCommand(new PercentType(50));
+ double[] dimmed = lsm.getRGBx();
+ assertEquals(4, dimmed.length);
+
+ // Brightness is ignored for this RGB data type.
+ assertArrayEquals(full, dimmed, 1.0);
+
+ lsm.setRGBx(new double[] { 0.0, 100.0, 200.0, 55.0 });
+
+ PercentType brightness = lsm.getBrightness(true);
+ assertNotNull(brightness);
+ assertEquals(new PercentType(50), brightness);
+
+ HSBType color = lsm.getColor();
+ assertNotNull(color);
+ assertEquals(50.0, color.getBrightness().doubleValue(), 1);
+ }
+
+ @Test
+ public void testRgbcwIgnoreBrightness() {
+ LightModel lsm = new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ RgbDataType.RGB_C_W_NO_BRIGHTNESS);
+
+ lsm.handleCommand(HSBType.RED);
+ assertEquals(HSBType.RED, lsm.getColor());
+ assertEquals(PercentType.HUNDRED, lsm.getBrightness(true));
+ assertEquals(OnOffType.ON, lsm.getOnOff(true));
+
+ double[] full = lsm.getRGBx();
+ assertEquals(5, full.length);
+ assertEquals(255.0, full[0], 1);
+ assertEquals(0.0, full[1], 1);
+ assertEquals(0.0, full[2], 1);
+ assertEquals(0.0, full[3], 1);
+ assertEquals(0.0, full[4], 1);
+
+ lsm.handleCommand(new PercentType(50));
+ double[] dimmed = lsm.getRGBx();
+ assertEquals(5, dimmed.length);
+
+ // Brightness is ignored for this RGB data type.
+ assertArrayEquals(full, dimmed, 1.0);
+
+ lsm.setRGBx(new double[] { 0.0, 100.0, 200.0, 40.0, 15.0 });
+
+ // In NO_BRIGHTNESS mode, brightness is preserved from cached HSB
+ // rather than derived from the incoming RGBx payload.
+ PercentType brightness = lsm.getBrightness(true);
+ assertNotNull(brightness);
+ assertEquals(new PercentType(50), brightness);
+
+ HSBType color = lsm.getColor();
+ assertNotNull(color);
+ assertEquals(50.0, color.getBrightness().doubleValue(), 1);
+ }
+
+ @Test
+ public void testRgbwWhiteOnlyDoesNotIgnoreBrightness() {
+ LightModel lsm = new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE, RgbDataType.RGB_W);
+
+ lsm.handleCommand(HSBType.RED);
+ double[] before = lsm.getRGBx();
+ assertEquals(255.0, before[0], 1);
+
+ lsm.setLedOperatingMode(LedOperatingMode.WHITE_ONLY);
+ lsm.setBrightness(50.0);
+ double[] rgbw = lsm.getRGBx();
+
+ // RGB channels must be zero
+ assertEquals(0.0, rgbw[0], 0.01);
+ assertEquals(0.0, rgbw[1], 0.01);
+ assertEquals(0.0, rgbw[2], 0.01);
+
+ // White channel must carry full brightness
+ assertEquals(127.5, rgbw[3], 1.0);
+ }
+
+ @Test
+ public void testRgbcwWhiteOnlyDoesNotIgnoreBrightness() {
+ LightModel lsm = new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE, RgbDataType.RGB_C_W);
+
+ lsm.handleCommand(HSBType.RED);
+ double[] before = lsm.getRGBx();
+ assertEquals(255.0, before[0], 1);
+
+ lsm.setLedOperatingMode(LedOperatingMode.WHITE_ONLY);
+ lsm.setBrightness(50.0);
+ double[] rgbcw = lsm.getRGBx();
+
+ // RGB channels must be zero
+ assertEquals(0.0, rgbcw[0], 0.01);
+ assertEquals(0.0, rgbcw[1], 0.01);
+ assertEquals(0.0, rgbcw[2], 0.01);
+
+ // CW + WW must equal full brightness
+ assertEquals(127.5, rgbcw[3] + rgbcw[4], 1.0);
+ }
+
+ @Test
+ public void testRgbwWhiteOnlyIgnoresBrightness() {
+ LightModel lsm = new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ RgbDataType.RGB_W_NO_BRIGHTNESS);
+
+ lsm.handleCommand(HSBType.RED);
+ double[] before = lsm.getRGBx();
+ assertEquals(255.0, before[0], 1);
+
+ lsm.setLedOperatingMode(LedOperatingMode.WHITE_ONLY);
+ lsm.setBrightness(50.0);
+ double[] rgbw = lsm.getRGBx();
+
+ // RGB channels must be zero
+ assertEquals(0.0, rgbw[0], 0.01);
+ assertEquals(0.0, rgbw[1], 0.01);
+ assertEquals(0.0, rgbw[2], 0.01);
+
+ // Brightness ignored → white channel stays at full
+ assertEquals(255.0, rgbw[3], 1.0);
+ }
+
+ @Test
+ public void testRgbcWhiteOnlyIgnoresBrightness() {
+ LightModel lsm = new LightModel(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ RgbDataType.RGB_C_W_NO_BRIGHTNESS);
+
+ lsm.handleCommand(HSBType.RED);
+ double[] before = lsm.getRGBx();
+ assertEquals(255.0, before[0], 1);
+
+ lsm.setLedOperatingMode(LedOperatingMode.WHITE_ONLY);
+ lsm.setBrightness(50.0);
+ double[] rgbcw = lsm.getRGBx();
+
+ // RGB channels must be zero
+ assertEquals(0.0, rgbcw[0], 0.01);
+ assertEquals(0.0, rgbcw[1], 0.01);
+ assertEquals(0.0, rgbcw[2], 0.01);
+
+ // Brightness ignored → CW + WW must equal full brightness
+ assertEquals(255.0, rgbcw[3] + rgbcw[4], 1.0);
+ }
+
+ @Test
+ void testMirekFromRgbwWhiteChannelOnly() {
+ LightModel lsm = new LightModel(LightModel.LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ LightModel.RgbDataType.RGB_W, null, null, null, null, 153.0, 500.0);
+ lsm.setLedOperatingMode(LightModel.LedOperatingMode.WHITE_ONLY);
+
+ double[] rgbw = new double[] { 0, 0, 0, 200 };
+ lsm.setRGBx(rgbw);
+
+ double expectedMirek = (153.0 + 500.0) / 2.0;
+ double expectedBrightness = 200.0 * 100.0 / 255.0;
+
+ assertEquals(expectedMirek, lsm.getMirek(), 0.1);
+ assertEquals(expectedBrightness, lsm.getBrightness(true).doubleValue(), 1.0);
+ }
+
+ @Test
+ void testMirekFromRgbcwWhiteChannelsOnly() {
+ LightModel lsm = new LightModel(LightModel.LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ LightModel.RgbDataType.RGB_C_W, null, null, null, null, 153.0, 500.0);
+ lsm.setLedOperatingMode(LightModel.LedOperatingMode.WHITE_ONLY);
+
+ double[] rgbcw = new double[] { 0, 0, 0, 100, 50 };
+ lsm.setRGBx(rgbcw);
+
+ double cw = 100;
+ double ww = 50;
+ double sum = cw + ww;
+ double expectedMirek = ((153.0 * cw) + (500.0 * ww)) / sum;
+ double expectedBrightness = (sum * 100.0 / 255.0);
+
+ assertEquals(expectedMirek, lsm.getMirek(), 0.1);
+ assertEquals(expectedBrightness, lsm.getBrightness(true).doubleValue(), 1.0);
+ }
+
+ @Test
+ void testMirekFromRgbwWhiteChannelOnlyNoBrightness() {
+ LightModel lsm = new LightModel(LightModel.LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ LightModel.RgbDataType.RGB_W_NO_BRIGHTNESS, null, null, null, null, 153.0, 500.0);
+ lsm.setLedOperatingMode(LightModel.LedOperatingMode.WHITE_ONLY);
+
+ double expectedBrightness = 88.0;
+ lsm.setBrightness(expectedBrightness);
+
+ double[] rgbw = new double[] { 0, 0, 0, 200 };
+ lsm.setRGBx(rgbw);
+
+ double expectedMirek = (153.0 + 500.0) / 2.0;
+
+ assertEquals(expectedMirek, lsm.getMirek(), 0.1);
+ assertEquals(expectedBrightness, lsm.getBrightness(true).doubleValue(), 1.0);
+ }
+
+ @Test
+ void testMirekFromRgbcwWhiteChannelsOnlyNoBrightness() {
+ LightModel lsm = new LightModel(LightModel.LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE,
+ LightModel.RgbDataType.RGB_C_W_NO_BRIGHTNESS, null, null, null, null, 153.0, 500.0);
+ lsm.setLedOperatingMode(LightModel.LedOperatingMode.WHITE_ONLY);
+
+ double expectedBrightness = 33.0;
+ lsm.setBrightness(expectedBrightness);
+
+ double[] rgbcw = new double[] { 0, 0, 0, 100, 50 };
+ lsm.setRGBx(rgbcw);
+
+ double cw = 100;
+ double ww = 50;
+ double sum = cw + ww;
+ double expectedMirek = ((153.0 * cw) + (500.0 * ww)) / sum;
+
+ assertEquals(expectedMirek, lsm.getMirek(), 0.1);
+ assertEquals(expectedBrightness, lsm.getBrightness(true).doubleValue(), 1.0);
+ }
}