1515import java .math .BigDecimal ;
1616import java .util .Arrays ;
1717import java .util .Objects ;
18+ import java .util .Set ;
1819
1920import org .eclipse .jdt .annotation .NonNullByDefault ;
2021import org .eclipse .jdt .annotation .Nullable ;
6364 * Also set {@link #rgbDataType} to the chosen RGB data type RGB, RGBW, RGBCW etc.
6465 * And optionally set the following configuration parameters:
6566 * <ul>
66- * <li>Optionally override {@link #minimumOnBrightness} to a minimum brightness percent in the range [0.1 ..10.0]
67+ * <li>Optionally override {@link #minimumOnBrightness} to a minimum brightness percent in the range [0.0 ..10.0]
6768 * percent, to consider as being "ON". The default is 1 percent.</li>
6869 * <li>Optionally override {@link #mirekControlWarmest} to a 'warmest' white color temperature in the range
6970 * [{@link #mirekControlCoolest}..1000.0] Mirek/Mired. The default is 500 Mirek/Mired.</li>
@@ -237,9 +238,30 @@ public static enum RgbDataType {
237238 /** supports 4-element RGB with white channel */
238239 RGB_W ,
239240 /** supports 5-element RGB with cold and warm white channels */
240- RGB_C_W
241+ RGB_C_W ,
242+ /** as RGB_W but ignores brightness (i.e. only HS parts of HSBType) */
243+ RGB_W_NO_BRIGHTNESS ,
244+ /** as RGB_C_W but ignores brightness (i.e. only HS parts of HSBType) */
245+ RGB_C_W_NO_BRIGHTNESS
241246 }
242247
248+ /**
249+ * Set of RGB data types that do not use the brightness part of the HSBType state.
250+ */
251+ private static final Set <RgbDataType > NO_BRIGHTNESS_TYPES = Set .of (RgbDataType .RGB_NO_BRIGHTNESS ,
252+ RgbDataType .RGB_W_NO_BRIGHTNESS , RgbDataType .RGB_C_W_NO_BRIGHTNESS );
253+
254+ /**
255+ * Set of RGB data types that use a white channel.
256+ */
257+ private static final Set <RgbDataType > RGB_W_TYPES = Set .of (RgbDataType .RGB_W , RgbDataType .RGB_W_NO_BRIGHTNESS );
258+
259+ /**
260+ * Set of RGB data types that use cold and warm white channels.
261+ */
262+ private static final Set <RgbDataType > RGB_C_W_TYPES = Set .of (RgbDataType .RGB_C_W ,
263+ RgbDataType .RGB_C_W_NO_BRIGHTNESS );
264+
243265 /**
244266 * Enum for the LED operating mode
245267 * <p>
@@ -518,9 +540,9 @@ public synchronized void configSetLightCapabilities(LightCapabilities lightCapab
518540 * @throws IllegalArgumentException if the minimumBrightness parameter is out of range.
519541 */
520542 public synchronized void configSetMinimumOnBrightness (double minimumOnBrightness ) throws IllegalArgumentException {
521- if (minimumOnBrightness < 0.1 || minimumOnBrightness > 10.0 ) {
543+ if (minimumOnBrightness < 0.0 || minimumOnBrightness > 10.0 ) {
522544 throw new IllegalArgumentException (
523- "Minimum brightness '%.1f' out of range [0.1 ..10.0]" .formatted (minimumOnBrightness ));
545+ "Minimum brightness '%.1f' out of range [0.0 ..10.0]" .formatted (minimumOnBrightness ));
524546 }
525547 this .minimumOnBrightness = minimumOnBrightness ;
526548 }
@@ -723,7 +745,7 @@ public synchronized double getMirek() {
723745 */
724746 public synchronized @ Nullable OnOffType getOnOff (boolean forceChannelVisible ) {
725747 return (!lightCapabilities .supportsColor () && !lightCapabilities .supportsBrightness ()) || forceChannelVisible
726- ? OnOffType .from (cachedHSB .getBrightness ().doubleValue () >= minimumOnBrightness )
748+ ? OnOffType .from (cachedHSB .getBrightness ().doubleValue () > minimumOnBrightness )
727749 : null ;
728750 }
729751
@@ -734,20 +756,20 @@ public synchronized double getMirek() {
734756 * follows:
735757 *
736758 * <ul>
737- * <li>'RGB_NO_BRIGHTNESS': The return result does not depend on the current brightness. In other words the values
738- * only relate to the 'HS' part of the {@link HSBType} state. Note: this means that in this case a round trip of
739- * setRGBx() followed by getRGBx() will NOT necessarily contain identical values, although the RGB ratios will
740- * certainly be the same.</li>
759+ * <li>'RGB_NO_BRIGHTNESS', 'RGB_W_NO_BRIGHTNESS', 'RGB_C_W_NO_BRIGHTNESS' : The return result does not depend on
760+ * the current brightness. In other words the values only relate to the 'HS' part of the {@link HSBType} state.
761+ * Note: this means that in this case a round trip of setRGBx() followed by getRGBx() will NOT necessarily contain
762+ * identical values, although the RGB ratios will certainly be the same.</li>
741763 *
742764 * <li>All other values of {@link #rgbDataType}: The return result depends on the current brightness. In other
743765 * words the values relate to all the 'HSB' parts of the {@link HSBType} state.</li>
744- * <ul>
766+ * </ ul>
745767 *
746768 * @return double[] representing the RGB(C)(W) components in range [0..255.0]
747769 * @throws IllegalStateException if the RGB data type is not compatible with the current LED operating mode.
748770 */
749771 public synchronized double [] getRGBx () throws IllegalStateException {
750- HSBType hsb = RgbDataType . RGB_NO_BRIGHTNESS == rgbDataType
772+ HSBType hsb = NO_BRIGHTNESS_TYPES . contains ( rgbDataType )
751773 ? new HSBType (cachedHSB .getHue (), cachedHSB .getSaturation (), PercentType .HUNDRED )
752774 : cachedHSB ;
753775
@@ -758,24 +780,24 @@ public synchronized double[] getRGBx() throws IllegalStateException {
758780 /*
759781 * If the light has a single white led then its value is determined by the brightness only.
760782 */
761- if (RgbDataType . RGB_W == rgbDataType ) {
762- double w = cachedHSB .getBrightness ().doubleValue () * 255.0 / 100.0 ;
783+ if (RGB_W_TYPES . contains ( rgbDataType ) ) {
784+ double w = hsb .getBrightness ().doubleValue () * 255.0 / 100.0 ;
763785 return new double [] { 0.0 , 0.0 , 0.0 , w };
764786 }
765787
766788 /*
767789 * If the light has a warm and a cool white led, the mix of white values are determined
768790 * by the brightness and the color temperature.
769791 */
770- if (RgbDataType . RGB_C_W == rgbDataType ) {
792+ if (RGB_C_W_TYPES . contains ( rgbDataType ) ) {
771793 double denominator = warmWhiteLed .getMirek () - coolWhiteLed .getMirek ();
772794 double ratio ;
773795 if (denominator > 0 && !Double .isNaN (cachedMirek )) {
774796 ratio = Math .max (0.0 , Math .min (1.0 , (cachedMirek - coolWhiteLed .getMirek ()) / denominator ));
775797 } else {
776798 ratio = 0.5 ;
777799 }
778- double bri = cachedHSB .getBrightness ().doubleValue () * 255.0 / 100.0 ;
800+ double bri = hsb .getBrightness ().doubleValue () * 255.0 / 100.0 ;
779801 double cool = bri * ratio ;
780802 double warm = bri - cool ;
781803 return new double [] { 0.0 , 0.0 , 0.0 , cool , warm };
@@ -794,9 +816,9 @@ public synchronized double[] getRGBx() throws IllegalStateException {
794816 */
795817 PercentType [] rgbP = ColorUtil .hsbToRgbPercent (hsb );
796818 double [] rgb = Arrays .stream (rgbP ).mapToDouble (p -> p .doubleValue () * 255.0 / 100.0 ).toArray ();
797- if (RgbDataType . RGB_W == rgbDataType ) {
819+ if (RGB_W_TYPES . contains ( rgbDataType ) ) {
798820 return new double [] { rgb [0 ], rgb [1 ], rgb [2 ], 0 };
799- } else if (RgbDataType . RGB_C_W == rgbDataType ) {
821+ } else if (RGB_C_W_TYPES . contains ( rgbDataType ) ) {
800822 return new double [] { rgb [0 ], rgb [1 ], rgb [2 ], 0 , 0 };
801823 }
802824 return rgb ;
@@ -806,7 +828,7 @@ public synchronized double[] getRGBx() throws IllegalStateException {
806828 * In combined mode the RGB and white values are all determined by the HSB values.
807829 */
808830 if (LedOperatingMode .COMBINED == ledOperatingMode ) {
809- if (RgbDataType . RGB_C_W == rgbDataType ) {
831+ if (RGB_C_W_TYPES . contains ( rgbDataType ) ) {
810832 /*
811833 * RGBCW - convert HSB to RGB, normalize it, then convert to RGBCW, then scale to [0..255]
812834 */
@@ -815,7 +837,7 @@ public synchronized double[] getRGBx() throws IllegalStateException {
815837 double [] rgbcw = RgbcwMath .rgb2rgbcw (rgb , coolWhiteLed .getProfile (), warmWhiteLed .getProfile ());
816838 rgbcw = Arrays .stream (rgbcw ).map (d -> Math .round (d * 255 * 10 ) / 10 ).toArray (); // // round to 1
817839 return rgbcw ;
818- } else if (RgbDataType . RGB_W == rgbDataType ) {
840+ } else if (RGB_W_TYPES . contains ( rgbDataType ) ) {
819841 /*
820842 * RGBW - convert HSB to RGBW, then scale to [0..255]
821843 */
@@ -1031,15 +1053,15 @@ public synchronized void setOnOff(boolean on) {
10311053 * on the value of {@link #rgbDataType} the brightness may or may not change as follows:
10321054 *
10331055 * <ul>
1034- * <li>'RGB_NO_BRIGHTNESS' both [255,0,0] and [127.5,0,0] change the color to RED without a change in brightness.
1035- * In other words the values only relate to the 'HS' part of the {@link HSBType} state. Note: this means that in
1036- * this case a round trip of 'setRGBx()' followed by 'getRGBx()' will NOT necessarily contain identical values,
1037- * although the RGB ratios will certainly be the same.</li>
1056+ * <li>'RGB_NO_BRIGHTNESS', 'RGB_W_NO_BRIGHTNESS', 'RGB_C_W_NO_BRIGHTNESS': The set value does not affect the
1057+ * current brightness. In other words the values only relate to the 'HS' part of the {@link HSBType} state. Note:
1058+ * this means that in this case a round trip of 'setRGBx()' followed by 'getRGBx()' will NOT necessarily contain
1059+ * identical values, although the RGB ratios will certainly be the same.</li>
10381060 *
10391061 * <li>All other values of {@link #rgbDataType}: both [255,0,0] and [127.5,0,0] change the color to RED and the
10401062 * former changes the brightness to 100 percent, whereas the latter changes it to 50 percent. In other words the
10411063 * values relate to all the 'HSB' parts of the {@link HSBType} state.</li>
1042- * <ul>
1064+ * </ ul>
10431065 *
10441066 * @param rgbxParameter an array of double representing RGB or RGBW values in range [0.0..255.0]
10451067 * @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
10491071 if (rgbxParameter .length > 5 ) {
10501072 throw new IllegalArgumentException ("Too many arguments in RGBx array" );
10511073 }
1052- if (rgbxParameter .length < 3 || (RgbDataType . RGB_W == rgbDataType && rgbxParameter .length < 4 )
1053- || (RgbDataType . RGB_C_W == rgbDataType && rgbxParameter .length < 5 )) {
1074+ if (rgbxParameter .length < 3 || (RGB_W_TYPES . contains ( rgbDataType ) && rgbxParameter .length < 4 )
1075+ || (RGB_C_W_TYPES . contains ( rgbDataType ) && rgbxParameter .length < 5 )) {
10541076 throw new IllegalArgumentException ("Too few arguments in RGBx array" );
10551077 }
10561078 if (rgbxParameter .length == 3 && ledOperatingMode != LedOperatingMode .RGB_ONLY ) {
@@ -1064,10 +1086,11 @@ public synchronized void setRGBx(double[] rgbxParameter) throws IllegalArgumentE
10641086 }
10651087
10661088 HSBType hsb ;
1089+ Double mirek ;
1090+ PercentType oldBri = cachedHSB .getBrightness ();
10671091 switch (ledOperatingMode ) {
10681092 case WHITE_ONLY :
10691093 double white ;
1070- double mirek ;
10711094 if (rgbxParameter .length == 5 ) {
10721095 /*
10731096 * 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
11141137
11151138 case COMBINED :
11161139 double [] rgbx ;
1117- if (RgbDataType . RGB_C_W == rgbDataType ) {
1140+ if (RGB_C_W_TYPES . contains ( rgbDataType ) ) {
11181141 // RGBCW - normalize, convert to RGB, then scale back to [0..255]
11191142 rgbx = Arrays .stream (rgbxParameter ).map (d -> d / 255.0 ).toArray ();
11201143 rgbx = RgbcwMath .rgbcw2rgb (rgbx , coolWhiteLed .getProfile (), warmWhiteLed .getProfile ());
@@ -1127,17 +1150,20 @@ public synchronized void setRGBx(double[] rgbxParameter) throws IllegalArgumentE
11271150 hsb = ColorUtil .rgbToHsb (Arrays .stream (rgbx ).map (d -> d * 100.0 / 255.0 )
11281151 .mapToObj (d -> zPercentTypeFrom (d )).toArray (PercentType []::new ));
11291152
1130- if (RgbDataType .RGB_NO_BRIGHTNESS == rgbDataType ) {
1131- hsb = new HSBType (hsb .getHue (), hsb .getSaturation (), cachedHSB .getBrightness ());
1132- }
1153+ mirek = zMirekFrom (hsb );
11331154 break ;
11341155
11351156 default :
11361157 return ; // safe coding but will never happen
11371158 }
11381159
1139- cachedHSB = hsb ;
1140- cachedMirek = zMirekFrom (hsb );
1160+ if (NO_BRIGHTNESS_TYPES .contains (rgbDataType )) {
1161+ cachedHSB = new HSBType (hsb .getHue (), hsb .getSaturation (), oldBri );
1162+ } else {
1163+ cachedHSB = hsb ;
1164+ zHandleBrightness (hsb .getBrightness ()); // refresh cached brightness and on/off state
1165+ }
1166+ cachedMirek = mirek ;
11411167 }
11421168
11431169 /**
@@ -1209,12 +1235,12 @@ public synchronized LightModel copy() {
12091235 * @param brightness the brightness {@link PercentType} to set.
12101236 */
12111237 private void zHandleBrightness (PercentType brightness ) {
1212- if (brightness .doubleValue () >= minimumOnBrightness ) {
1238+ if (brightness .doubleValue () > minimumOnBrightness ) {
12131239 cachedBrightness = brightness ;
12141240 cachedHSB = new HSBType (cachedHSB .getHue (), cachedHSB .getSaturation (), brightness );
12151241 cachedOnOff = OnOffType .ON ;
12161242 } else {
1217- if (OnOffType .ON == cachedOnOff && cachedHSB .getBrightness ().doubleValue () >= minimumOnBrightness ) {
1243+ if (OnOffType .ON == cachedOnOff && cachedHSB .getBrightness ().doubleValue () > minimumOnBrightness ) {
12181244 cachedBrightness = cachedHSB .getBrightness (); // cache the last 'ON' state brightness
12191245 }
12201246 cachedHSB = new HSBType (cachedHSB .getHue (), cachedHSB .getSaturation (), PercentType .ZERO );
0 commit comments