Open mueller-ma opened 1 year ago
For a color item, we have already the colorpicker widget to select the color, the slider widget to select its brightness and the switch widget to turn it on or off.
For color temperature, we could imagine either a new widget or an option to the existing colorpicker widget. In both cases, we need to implement a new UI in all apps. It could be a simple slider ? As a min and max values are probably required, a new widget similar to slider widget would be better I believe. We could name it colortemperature.
To be thought how an UI can then build the command to be sent to the server.
For a color item, we have already the colorpicker widget to select the color
The colorpicker can also control the brightness, which is very nice.
When making the color temperature a new option for the colorpicker, we solve two issues:
The state is transmitted as HSB and the binding then has to parse brightness and color temperature from that single state.
Color temperature channels are either Dimmers or Numbers though, not Colors, so I don't think that idea will fly.
For controlling color temperature channels, the client would need to differentiate between dimmer (relative) or absolute channels (by item type):
Ok, so then it's required to add two items to one widget, like ColorTemperature brightnessItem=foo temperatureItem=bar
. This is probably the first widget with more than one item. Is this an argument against it?
You could consider to do it like the Philips Hue App -- see screenshots below..
We have two system channel types for color temperature. The first expects a Dimmer item: https://github.com/openhab/openhab-core/blob/6d0a3b330c48cdb1d6112bcf001bed3f11ed4094/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/DefaultSystemChannelTypeProvider.java#L233 The second expects a Number:Temperature item: https://github.com/openhab/openhab-core/blob/6d0a3b330c48cdb1d6112bcf001bed3f11ed4094/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/DefaultSystemChannelTypeProvider.java#L244 For both of them, it looks natural to use a Slider widget.
But I agree that for the second we could imagine a new dedicated widget showing the rendering of white, I imagine it as a slider + an image showing a gradient of temperatures but each app could implement it as it prefers. The idea would be to handle a kelvin (or mireds) value. Are you ok with the idea of a new sitemap element for that ? For Baisc UI, I need to find an image showing a color gradient between 1000 and 10000 K. The widget will look similar to the color widget but with a rectangle instead of a circle. Something like: https://upload.wikimedia.org/wikipedia/commons/e/e9/Color_temperature_black_body_800-12200K.svg
For this new sitemap element, I imagine in Basic UI the same rendering as for a Slider element but with an additional button allowing to open a popup to pick a color temperature.
@andrewfg : do you know mathematics to convert a kelvin temperature into a RGB value ?
In OH core ColorUtil we have public methods for converting everything to everything, and back. For your specific case of Kelvin to RGB one would use kelvinToXY()
=> xyToHSB()
=> hsbToRGB()
..
PS apropos this issue in general: As the OP has remarked, most light bindings have two ways of setting the Color Temperature -- namely
It is important to note that in the latter case the respective 0% and 100% points relate to the minimum and maximum Mirek capabilities of the actual lamp being controlled. So, in other words, if you have two lamps from different manufacturers with different colour temperature capabilities, then any particular slider position (say 42%) could relate to a totally different colour temperature.
Therefore for the purposes of this PR I would strongly suggest to focus only on a UI element that produces an absolute QuantityType:Temperature value. i.e. forget about the 0..100% slider stuff..
Yes, that is also my idea.
I prefer a separate widget that could also be considered when Default widget is set and the item is of type Number with temperature dimension and has colorTemperature tag.
I would name it Colortemperaturepicker.
@mueller-ma : is it also ok for you?
I can implement the core part quickly, it is only few new lines of code.
Reading the first messages, I realize that this will provide only color temperature control, not brightness control in the same widget. As brightness and color temperature are not part of the same item, we have no choice. Having a widget linked to 2 different items would be a massive change. Or we should rethink the Color item to embed optionally color temperature but that would be another big change.
But having two widgets, one for brightness and one for colour temperature is acceptable I believe.
^ Think of the example of the Hue App (see below) it supports 4 widgets -- namely On/Off, Brightness / Color Picker / Colour Temperature Picker. (Not to mention the other Hue specific widgets for Effects etc.)
The issue is mostly above having one widget for brightness and temperature. As I suggested in my first post, the color picker could be limited to temperatures of white.
@lolodomo also when you implement something, please remember that the widget output QuantityType:Temperature should NOT produce a linear output in Kelvin. This is because the human eye does not perceive a linear Kelvin transition as a linear visual transition. That is the reason why the inverse Kelvin Mirek unit was invented. The human eye DOES perceive a linear Mirek transition as a linear visual effect.
I understand that my proposal does not respond to your need! What would be your concrete proposal that we can implement in OH considering the existing constraints?
That is the reason why the inverse Kelvin Mirek unit was invented. The human eye DOES perceive a linear Mirek transition as a linear visual effect.
If this is better, we can consider mired instead of Kelvin.
Think of the example of the Hue App (see below) it supports 4 widgets -- namely On/Off, Brightness / Color Picker / Colour Temperature Picker.
So that's fine to have different widgets?
As I suggested in my first post, the color picker could be limited to temperatures of white.
I don't see how that would work (in terms of syntax) though. A color picker for color and brightness needs only one item (as brightness is part of color), a color picker for color temperature would need two items (color temperature and brightness). How should core and/or main UI validate that properly?
The hue example shows clearly different "widgets", we could have the same approach in openHAB. We already have these existing widgets:
My proposal is to add a fourth widget for colour temperature (but not combined with brightness)..
if this is better, we can consider mired instead of Kelvin.
You must definitely do this. It must produce QuantityType:Temperature where the linear transition from one end of the widget control to the other produces a linear output in Mirek. Typically the lowest Mirek is 153 (aka 6500 Kelvin) and the highest Mirek is 500 (aka 2000 Kelvin)
add a fourth widget for colour temperature (but not combined with brightness)..
Makes sense.
@andrewfg : I will have more difficulty to find an image with a gradient in mired instead of Kelvin.
more difficulty to find an image with a gradient in mired instead of Kelvin.
Umm. That is not really a reason to do it wrong. And you could use the gradient that the OP copied from Hue above. IMHO the Hue engineers do really have a solid understanding.
I found this one but the values looks wrong, like negative. https://commons.wikimedia.org/wiki/File:Color_temperature_black_body_mired.svg
^ @lolodomo if you want, I can try to (mis) use the ColorUtils methods to programmatically produce a table of RGB values that match the proper gradient. Advantage of doing it that way would be getting a perfect match between UI and OH Java code. (Always assuming your display has proper color calibration). I would give you a table with 101 RGB values matching the Mirek scale.
PS you also need to decide the min/max range of the scale. It seems that max Mirek 500 (2000 K) is fairly non controversial. And in OH bindings (resp. actual lamps) min Mirek 153 (6500 K) is common. But others seem to prefer min Mirek 100 (10000 K). The latter would perhaps be more future proof for future much bluer lamps, but would overflow for lamps like Hue or Zigbee ones.
the values looks wrong
Agreed. ):
I will create some gradients tomorrow. What image format do you prefer (e.g. png etc.) and what width (in pixels)? Note pixel height is trivial since each row is the same..
Looking at the current color picker in Basic UI, I see the gradient for brightness dynamically adjusted with the selected colour. I assume it is computed, I have to check how. Maybe I could replace it with a computed gradient of mired values.
If not possible, we could have an image with a linear gradient between 100 and 500 mired. Same width as the colorwheel in the colour picker (to be checked). Colorwheel image is in PNG format so let's go with same format. I will reject selection of temperature outside the min/max of the current light. If the user clicks < min in the image, I will adjust to min. Similar for max.
But if I could build dynamically the gradient, that would be even better.
@andrewfg : A PNG file 300x300 pixels would be fine.
Regarding computed gradient for (background) brightness slider, the CSS linear-gradient
function is used combined with RGBA values allowing a gradient (based on transparency and a background color). We can't use that for color temperature, at least not the same way but one idea would to use this linear-gradient
function and provide to it a good amount of RGB values (10 or 20) to finally get something that will look like the gradient we are looking for. For that, I will need conversion from mired to RGB and I guess I should be able to compute the different values between min and max mired in Java and then provide them to the JavaScript to build the gradient. To be studied. In case it works, no need for an image containing the hardcoded gradient.
An Kelvin-to-RGB algorithm can be found here. Kelvin-to-Mired is easy: Mired * Kelvin = 1000000.
Kelvin-to-RGB algorithm can be found
We already have enough conversions in OH Core ColorUtils. There is no need to reinvent the wheel..
need conversion from mired to RGB
Try this..
rgb = ColorUtil.hsbToRGB(ColorUtil.xyToHSB(ColorUtil.kelvinToXY(1000000 / mirek)))
I will first define the new sitemap element in core framework if that's fine for everybody.
@andrewfg : don't loose your time for me, I believe I should be able to create a good linear gradient using the CSS function linear-gradient. So I don't need any PNG image.
create a good linear gradient using the CSS function linear-gradient.
I agree. If I would have done it, I would have created an SVG image that uses the same linear gradient function. Probably the only thing you need to tweak is the step size for the sub gradient parts. I would perhaps start with 20 sub gradients at step size of 5% .. but eventually it could go smaller (or larger)..
Just to mention that the ColorUtil.kelvinToXy()
method currently only handles 2000 K to 6500 K (500 Mk to 153 Mk) so it won’t return proper RGB values for higher colour temperatures. And if you eventually want the control to go to higher colour temperatures (e.g. 10000 K / 100 Mk) then I shall need to extend the internal lookup table. It is easily done, so let me know.
In practice, range 2000-6500 K should probably be fine. But the default range of the color temperature system channel is 1000-10000 K.
^ @lolodomo OK. Let me know if you need any changes from my side.
What I could do is a failback to a standard slider in case the range is larger than 2000-6500 K.
What I could do is a failback to a standard slider in case the range is larger than 2000-6500 K.
No, we can't do that as this will trigger the failback for any binding using the system channel type.
@andrewfg : I believe the best option is that you extend the range please.
the best option is that you extend the range please.
Ok. I will do it.
@lolodomo following is a link to my PR to extend the colour temperature range of the kelvinToXy()
method from 2000 .. 6500 K to 1000 .. 10000 K
https://github.com/openhab/openhab-core/pull/4429
Please note that the inverse xyToKelvin()
method (which is based on McCamy's approximation) is only accurate above 2000 K. This means that round trip conversions xyToKelvin(kelvinToXy())
are very inaccurate for colour temperatures below 2000 K. This is not perfect but, as we have observed above, it seems that most real bindings in OH are using a range of 2000 K to 6500 K .. where the accuracy is better than 1%..
@andrewfg : ok, no problem.
Here is the current result with a step of 5% between 154 and 400 mired (2500 K to 6500 K):
Looks not so bad,no ? Do you think I should provide more values ?
It would be interesting to see if 100 Mk results in a blueish colour, as I would expect..
With the current code (I mean without your extended values), it leads to that for a gradient between 100 mired and 1000 mired (1000 K to 10000 K).
All values outside the supported range are leading to an exception. It leads to that:
rgba(255, 251, 234, 1) 10%, rgba(255, 239, 199, 1) 15%, rgba(255, 228, 169, 1) 20%, rgba(255, 217, 141, 1) 25%, rgba(255, 206, 118, 1) 30%, rgba(255, 196, 97, 1) 35%, rgba(255, 186, 80, 1) 40%
I am asking myself if I have the expected values. As you say, we should have more blue white ?
For the "standard" range 154-400 mired, the computed values are:
rgba(244, 250, 255, 1) 0%, rgba(253, 255, 253, 1) 5%, rgba(255, 254, 244, 1) 10%, rgba(255, 251, 234, 1) 15%, rgba(255, 248, 224, 1) 20%, rgba(255, 245, 214, 1) 25%, rgba(255, 241, 205, 1) 30%, rgba(255, 238, 196, 1) 35%, rgba(255, 235, 187, 1) 40%, rgba(255, 232, 178, 1) 45%, rgba(255, 229, 171, 1) 50%, rgba(255, 226, 163, 1) 55%, rgba(255, 223, 155, 1) 60%, rgba(255, 219, 147, 1) 65%, rgba(255, 217, 141, 1) 70%, rgba(255, 214, 134, 1) 75%, rgba(255, 210, 127, 1) 80%, rgba(255, 207, 121, 1) 85%, rgba(255, 205, 115, 1) 90%, rgba(255, 201, 109, 1) 95%, rgba(255, 199, 104, 1) 100%
My current code:
long minMired = Units.KELVIN.toString().equals(unit) ? Math.round(1000000.0 / max.longValue())
: min.longValue();
long maxMired = Units.KELVIN.toString().equals(unit) ? Math.round(1000000.0 / min.longValue())
: max.longValue();
StringBuilder gradientBuilder = new StringBuilder();
for (int percent = 0; percent <= 100; percent += 5) {
double valueMired = (maxMired - minMired) * percent / 100.0 + minMired;
double valueKelvin = 1000000.0 / valueMired;
try {
int[] rgb = ColorUtil.hsbToRgb(ColorUtil.xyToHsb(ColorUtil.kelvinToXY(valueKelvin)));
logger.debug("Gradient {}%: {} mired => {} K => RGB {} {} {}", percent, valueMired,
valueKelvin, rgb[0], rgb[1], rgb[2]);
gradientBuilder.append(
"rgba(%d, %d, %d, 1) %d%%, ".formatted(rgb[0], rgb[1], rgb[2], percent));
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
logger.debug("Can't get RGB for {} Kelvin, ignoring it", valueKelvin);
}
}
if (gradientBuilder.length() > 2) {
gradientColors = gradientBuilder.substring(0, gradientBuilder.length() - 2);
}
@lolodomo can you please advise how you would plan to set the min/max Mirek range of the CT UI slider? Do you plan to have a fixed range ( 100 .. 1000 )? And/or will the UI control have a configuration screen to override the min/max?
Reason why I ask is that some bindings can provide the range from within code. For example the Hue binding API V2 provides min/max Mirek on a lamp by lamp basis. So more performant lamps could show a wider UI gradient than less performant ones. We would need, however, to create a mechanism whereby the UI can query the Item and in turn query the Channel to get meta data about the respective actual min/max range..
Min and max are two parameters of the new sitemap element. So you can choose the range you like in the sitemap. If not set, I try to get the range from the item state description. This will be the standard use case I believe. I finally consider the range 100-1000 mired as default if nothing is defined in sitemap/item.
Is your feature request related to a problem? Please describe.
For some time I created two sliders for lightbulbs that can change their color temperature. One for brightness, one for color temperature. Recently I acquired a Nanoleaf Elements light (LEDs with brightness and color temperature settings). The binding allows to use a color picker to control both brightness and color temperature settings. All colors expect in the range cold white to warm white are ignored by the binding, but I can control both with just one widget.
Describe the solution you'd like
Add a new option to the
Color
widget (e.g.temperatureOnly
) that limits the color selection from cold white to warm white.Example from the Ikea Tradfri app:
Describe alternatives you've considered
Additional context
Coordination between maintainers
Notify maintainers of other UIs: @openhab/webui-maintainers @openhab/android-maintainers @openhab/ios-maintainers
Checklist for implementation: