Skip to content

Commit 7ec362a

Browse files
committed
first stab at RGBCW support
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
1 parent 7e4d837 commit 7ec362a

3 files changed

Lines changed: 118 additions & 50 deletions

File tree

bundles/org.openhab.core/src/main/java/org/openhab/core/util/LightModel.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,15 @@ public class LightModel {
189189
* <li>'supportsColor' is true (the light supports color control)</li>
190190
* <li>'rgbLinkedToBrightness' is false (the RGB values are not linked to 'B' part of {@link HSBType}))</li>
191191
* <li>'supportsRgbWhite' is false (the light does not support RGB with White)</li>
192+
* <li>'supportsRgbCoolWarmWhite' is false (the light does not support RGBCW)</li>
192193
* <li>'minimumOnBrightness' is 1.0 (the minimum brightness percent to consider as light "ON")</li>
193194
* <li>'warmestMired' is 500 (the 'warmest' white color temperature)</li>
194195
* <li>'coolestMired' is 153 (the 'coolest' white color temperature)</li>
195196
* <li>'stepSize' is 10.0 (the step size for IncreaseDecreaseType commands)</li>
196197
* </ul>
197198
*/
198199
public LightModel() {
199-
this(true, true, true, false, false, null, null, null, null);
200+
this(true, true, true, false, false, false, null, null, null, null);
200201
}
201202

202203
/**
@@ -207,11 +208,12 @@ public LightModel() {
207208
* @param supportsColor true if the light supports color control
208209
* @param rgbLinkedToBrightness true if RGB values are linked with the 'B' part of the {@link HSBType}
209210
* @param supportsRgbWhite true if the light supports RGBW rather than RGB color control
211+
* @param supportsRgbCoolWarmWhite true if the light supports RGBCW color control
210212
*/
211213
public LightModel(boolean supportsBrightness, boolean supportsColorTemperature, boolean supportsColor,
212-
boolean rgbLinkedToBrightness, boolean supportsRgbWhite) {
213-
this(supportsBrightness, supportsColorTemperature, supportsColor, rgbLinkedToBrightness, supportsRgbWhite, null,
214-
null, null, null);
214+
boolean rgbLinkedToBrightness, boolean supportsRgbWhite, boolean supportsRgbCoolWarmWhite) {
215+
this(supportsBrightness, supportsColorTemperature, supportsColor, rgbLinkedToBrightness, supportsRgbWhite,
216+
supportsRgbCoolWarmWhite, null, null, null, null);
215217
}
216218

217219
/**
@@ -223,19 +225,21 @@ public LightModel(boolean supportsBrightness, boolean supportsColorTemperature,
223225
* @param supportsColor true if the light supports color control
224226
* @param rgbLinkedToBrightness true if RGB values are linked with the 'B' part of the {@link HSBType}
225227
* @param supportsRgbWhite true if the light supports RGBW rather than RGB color control
228+
* @param supportsRgbCoolWarmWhite true if the light supports RGBCW color control
226229
* @param minimumOnBrightness the minimum brightness percent to consider as light "ON"
227230
* @param warmestMired the 'warmest' white color temperature in Mired
228231
* @param coolestMired the 'coolest' white color temperature in Mired
229232
* @param stepSize the step size for IncreaseDecreaseType commands
230233
* @throws IllegalArgumentException if any of the parameters are out of range
231234
*/
232235
public LightModel(boolean supportsBrightness, boolean supportsColorTemperature, boolean supportsColor,
233-
boolean rgbLinkedToBrightness, boolean supportsRgbWhite, @Nullable Double minimumOnBrightness,
234-
@Nullable Double warmestMired, @Nullable Double coolestMired, @Nullable Double stepSize)
235-
throws IllegalArgumentException {
236+
boolean rgbLinkedToBrightness, boolean supportsRgbWhite, boolean supportsRgbCoolWarmWhite,
237+
@Nullable Double minimumOnBrightness, @Nullable Double warmestMired, @Nullable Double coolestMired,
238+
@Nullable Double stepSize) throws IllegalArgumentException {
236239
// instantiate an inline implementation of the abstract logic implementation class
237240
model = new LightModelAbstractLogicImpl(supportsBrightness, supportsColorTemperature, supportsColor,
238-
rgbLinkedToBrightness, supportsRgbWhite, minimumOnBrightness, warmestMired, coolestMired, stepSize) {
241+
rgbLinkedToBrightness, supportsRgbWhite, supportsRgbCoolWarmWhite, minimumOnBrightness, warmestMired,
242+
coolestMired, stepSize) {
239243
};
240244
}
241245

@@ -478,10 +482,11 @@ public double getMired() {
478482
}
479483

480484
/**
481-
* Runtime State: get the RGB(W) values as an array of doubles in range [0..255]. Depending on the value of
482-
* 'supportsRgbWhite', the array length is either 3 (RGB) or 4 for (RGBW). The array is in the order [red, green,
483-
* blue, (white)]. Depending on the value of 'supportsRgbDimming}', the brightness may or may not be used
484-
* follows:
485+
* Runtime State: get the RGB(C)(W) values as an array of doubles in range [0..255]. Depending on the value of
486+
* '{@link supportsRgbWhite}' and {@link supportsRgbColdWarmWhite}, the array length is either 3 (RGB), 4 (RGBW),
487+
* or 5 (RGBCW). The array is in the order [red, green, blue, (cold-)(white), (warm-white)].
488+
*
489+
* Depending on the value of '{@link supportsRgbDimming}', the brightness may or may not be used as follows:
485490
*
486491
* <ul>
487492
* <li>{@code supportsRgbDimming == false}: The return result does not depend on the current brightness. In other
@@ -493,7 +498,7 @@ public double getMired() {
493498
* values relate to all the 'HSB' parts of the {@link HSBType} state.</li>
494499
* <ul>
495500
*
496-
* @return double[] representing the RGB(W) components in range [0..255.0]
501+
* @return double[] representing the RGB(C)(W) components in range [0..255.0]
497502
*/
498503
public double[] getRGBx() {
499504
return model.getRGBx();

bundles/org.openhab.core/src/main/java/org/openhab/core/util/LightModelAbstractLogicImpl.java

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ abstract class LightModelAbstractLogicImpl {
5454
private boolean supportsColor = false; // true if the light supports color
5555
private boolean rgbLinkedToBrightness = false; // true if RGB(W) values are linked to the brightness
5656
private boolean supportsRgbWhite = false; // true if the light supports RGB with White
57+
private boolean supportsRgbCoolWarmWhite = false; // true if the light supports RGB with Cool+Warm White
5758
private boolean supportsBrightness = false; // true if the light supports brightness
5859
private boolean supportsColorTemperature = false; // true if the light supports color temperature
5960

@@ -79,18 +80,21 @@ abstract class LightModelAbstractLogicImpl {
7980
* @param supportsColor true if the light supports color control
8081
* @param rgbLinkedToBrightness true if RGB values are linked with the 'B' part of the {@link HSBType}
8182
* @param supportsRgbWhite true if the light supports RGBW rather than RGB color control
83+
* @param supportsRgbCoolWarmWhite true if the light supports RGBCW rather than RGB or RGBW color control
8284
* @param minimumOnBrightness the minimum brightness percent to consider as light "ON"
8385
* @param warmestMired the 'warmest' white color temperature in Mired
8486
* @param coolestMired the 'coolest' white color temperature in Mired
8587
* @param stepSize the step size for IncreaseDecreaseType commands
8688
*/
8789
LightModelAbstractLogicImpl(boolean supportsBrightness, boolean supportsColorTemperature, boolean supportsColor,
88-
boolean rgbLinkedToBrightness, boolean supportsRgbWhite, @Nullable Double minimumOnBrightness,
89-
@Nullable Double warmestMired, @Nullable Double coolestMired, @Nullable Double stepSize) {
90+
boolean rgbLinkedToBrightness, boolean supportsRgbWhite, boolean supportsRgbCoolWarmWhite,
91+
@Nullable Double minimumOnBrightness, @Nullable Double warmestMired, @Nullable Double coolestMired,
92+
@Nullable Double stepSize) {
9093
this.supportsColor = supportsColor || supportsRgbWhite || rgbLinkedToBrightness;
9194
this.supportsBrightness = supportsBrightness || supportsColor || supportsRgbWhite || rgbLinkedToBrightness;
9295
this.supportsColorTemperature = supportsColorTemperature;
9396
this.supportsRgbWhite = supportsRgbWhite;
97+
this.supportsRgbCoolWarmWhite = supportsRgbCoolWarmWhite;
9498
this.rgbLinkedToBrightness = rgbLinkedToBrightness;
9599
this.minimumOnBrightness = minimumOnBrightness != null ? minimumOnBrightness : this.minimumOnBrightness;
96100
this.warmestMired = warmestMired != null ? warmestMired : this.warmestMired;
@@ -167,6 +171,13 @@ boolean configGetSupportsColorTemperature() {
167171
return supportsColorTemperature;
168172
}
169173

174+
/**
175+
* Configuration: check if RGBCW color control is supported
176+
*/
177+
boolean configGetSupportsRgbCoolWarmWhite() {
178+
return supportsRgbCoolWarmWhite;
179+
}
180+
170181
/**
171182
* Configuration: check if RGBW color control is supported versus RGB only
172183
*/
@@ -283,6 +294,15 @@ void configSetSupportsColorTemperature(boolean supportsColorTemperature) {
283294
this.supportsColorTemperature = supportsColorTemperature;
284295
}
285296

297+
/**
298+
* Configuration: set whether RGBCW color control is supported
299+
*
300+
* @param supportsRgbCoolWarmWhite true if RGBW color control is supported
301+
*/
302+
void configSetSupportsRgbCoolWarmWhite(boolean supportsRgbCoolWarmWhite) {
303+
this.supportsRgbCoolWarmWhite = supportsRgbCoolWarmWhite;
304+
}
305+
286306
/**
287307
* Configuration: set whether RGBW color control is supported versus RGB only
288308
*
@@ -380,10 +400,11 @@ OnOffType getOnOff(boolean... forceChannelVisible) {
380400
}
381401

382402
/**
383-
* Runtime State: get the RGB(W) values as an array of doubles in range [0..255]. Depending on the value of '{@link
384-
* supportsRgbWhite}', the array length is either 3 (RGB) or 4 for (RGBW). The array is in the order [red, green,
385-
* blue, (white)]. Depending on the value of '{@link supportsRgbDimming}', the brightness may or may not be used
386-
* follows:
403+
* Runtime State: get the RGB(C)(W) values as an array of doubles in range [0..255]. Depending on the value of
404+
* '{@link supportsRgbWhite}' and {@link supportsRgbColdWarmWhite}, the array length is either 3 (RGB), 4 (RGBW),
405+
* or 5 (RGBCW). The array is in the order [red, green, blue, (cold-)(white), (warm-white)].
406+
*
407+
* Depending on the value of '{@link supportsRgbDimming}', the brightness may or may not be used as follows:
387408
*
388409
* <ul>
389410
* <li>{@code supportsRgbDimming == false}: The return result does not depend on the current brightness. In other
@@ -395,13 +416,29 @@ OnOffType getOnOff(boolean... forceChannelVisible) {
395416
* values relate to all the 'HSB' parts of the {@link HSBType} state.</li>
396417
* <ul>
397418
*
398-
* @return double[] representing the RGB(W) components in range [0..255.0]
419+
* @return double[] representing the RGB(C)(W) components in range [0..255.0]
399420
*/
400421
double[] getRGBx() {
401422
HSBType hsb = rgbLinkedToBrightness ? cachedColor
402423
: new HSBType(cachedColor.getHue(), cachedColor.getSaturation(), PercentType.HUNDRED);
403-
PercentType[] rgbw = supportsRgbWhite ? ColorUtil.hsbToRgbwPercent(hsb) : ColorUtil.hsbToRgbPercent(hsb);
404-
return Arrays.stream(rgbw).mapToDouble(p -> p.doubleValue() * 255.0 / 100.0).toArray();
424+
if (!supportsRgbCoolWarmWhite) {
425+
// use ColorUtils to get either RGB or RGBW
426+
PercentType[] rgbx = supportsRgbWhite ? ColorUtil.hsbToRgbwPercent(hsb) : ColorUtil.hsbToRgbPercent(hsb);
427+
return Arrays.stream(rgbx).mapToDouble(p -> p.doubleValue() * 255.0 / 100.0).toArray();
428+
} else {
429+
// use own code to get RGBCW (consider moving this to ColorUtils later in a second step)
430+
PercentType[] rgbPct = ColorUtil.hsbToRgbPercent(hsb);
431+
double[] rgb = Arrays.stream(rgbPct).mapToDouble(p -> p.doubleValue() * 255.0 / 100.0).toArray();
432+
double[] rgbcw = new double[5];
433+
if (Arrays.stream(rgb).max().orElse(0.0) > 0.0) {
434+
double white = Arrays.stream(rgb).min().orElse(0.0);
435+
System.arraycopy(Arrays.stream(rgb).map(c -> c - white).toArray(), 0, rgbcw, 0, 3);
436+
double cool = (warmestMired - cachedMired) / (warmestMired - coolestMired);
437+
rgbcw[3] = cool * white;
438+
rgbcw[4] = (1.0 - cool) * white;
439+
}
440+
return rgbcw;
441+
}
405442
}
406443

407444
/**
@@ -512,10 +549,10 @@ void setMired(double mired) throws IllegalArgumentException {
512549
}
513550

514551
/**
515-
* Runtime State: update the color with RGB(W) fields from the remote light, and update the cached HSB color
516-
* accordingly. The array must be in the order [red, green, blue, (white)]. If white is present but the light does
517-
* not support white channel then IllegalArgumentException is thrown. Depending on the value of
518-
* '{@link supportsRgbDimming}', the brightness may or may not change as follows:
552+
* Runtime State: update the color with RGB(C)(W) fields from the remote light, and update the cached HSB color
553+
* accordingly. The array must be in the order [red, green, blue, (cold)(white), (warm-white)]. If white is
554+
* present but the light does not support white channelS then IllegalArgumentException is thrown. Depending on
555+
* the value of '{@link supportsRgbDimming}', the brightness may or may not change as follows:
519556
*
520557
* <ul>
521558
* <li>{@code supportsRgbDimming == false} both [255,0,0] and [127.5,0,0] change the color to RED without a change
@@ -531,8 +568,24 @@ void setMired(double mired) throws IllegalArgumentException {
531568
* @param rgbx an array of double representing RGB or RGBW values in range [0..255]
532569
*/
533570
void setRGBx(double[] rgbx) throws IllegalArgumentException {
534-
if (rgbx.length > 3 && !supportsRgbWhite) {
535-
throw new IllegalArgumentException("Light does not support white channel");
571+
if (rgbx.length > 5) {
572+
throw new IllegalArgumentException("Too many arguments in RGBx array");
573+
}
574+
if (rgbx.length == 5) {
575+
if (!supportsRgbCoolWarmWhite) {
576+
throw new IllegalArgumentException("Light does not support RGBCW");
577+
}
578+
/*
579+
* TODO implement RGBCW setting..
580+
* This requires a conversion from RGBCW to RGBW and then to HS(B).
581+
* This conversion is not entirely straightforward because it affects color temperature too.
582+
* Also it is not clear how to handle the 'B' part of HSB in the case of !rgbLinkedToBrightness.
583+
* For now, just ignore the cold and warm white channels..
584+
*/
585+
return;
586+
}
587+
if (rgbx.length == 4 && !supportsRgbWhite) {
588+
throw new IllegalArgumentException("Light does not support RGBW");
536589
}
537590
HSBType dimmedHSB = ColorUtil.rgbToHsb(Arrays.stream(rgbx).map(d -> d * 100.0 / 255.0)
538591
.mapToObj(d -> zInternalPercentTypeOf(d)).toArray(PercentType[]::new));

0 commit comments

Comments
 (0)