[SOLVED] Convert HSBType to CIE XY needed for Ikea Tradfri control through Deconz REST

Hello.
I’m not able to find a conversion from Color/Colorpicker HSBType to CIE xy values needed by Deconz/Conbee for controlling Tradfri CWS bulbs.
Has anybody come across it somewhere? ( colormode(hs) does not work for Ikea lights)

Why not just use the Ikea Gateway and binding?
Since I’m already using Deconz/Conbee for my MI/Aqara Cubes, I’m giving the Ikea Tradfri bulbs a test-ride to see how long they work without any hiccups.

My Ikea Gateway works OK, but goes offline maybe twice a month. (have changed patch cable and power)

Other nice features with deconz are:

  • simultaneous ON/OFF action of larger groups.
    (Native OH2 groups involving 6-12 Tradfri bulbs can take up to 5-6s to settle.)
  • native support for alert ie blink lights or whole group.
    (can do away with OH2 rule/timers for blinking manually)
  • Ikea 5 button remote is supported. (possibly other remotes/sensors as well, but I haven’t tested)

Only thing missing now is the HSBType to CIE xy color space conversion. :slight_smile:

I added these methods to HSBType a few months ago to support ZigBee and Tradfri, so they should be available in 2.4 at least, and probably 2.3.

Nice! :slight_smile:
And where is that exactly? In the Zigbee binding source code perhaps?

No - as above, it’s in the HSBType. I forget the exact method names, but it should be obvious (fromXy and toXy, or something like that).

1 Like

Figured it out after some hours of fiddling :slight_smile:
Posting back in case others will need this.

Virtual.items

  Color  vBulbTrumpet4Color
  String Trumpet4PolledBri { http="<[LightsCache:900:JSONPATH($.30.state.bri)]" }

.rules:

val String deconzHost = "http://127.0.0.1:8090/"
val String lightTrumpet4 = "api/0950775C33/lights/30/state"

rule "Trumpet 4 - Color"
when
    Item vBulbTrumpet4Color changed
then
    var String result = ""

    var HSBType hsbValue = vBulbTrumpet4Color.state as HSBType
    logInfo("deconz", "vBulbTrumpet4Color=" + hsbValue)
    
    val xyY = hsbValue.toXY.toString // convert to xyY
    val double x = Float::parseFloat(xyY.split(",").get(0).substring(1)) / 100.0 // scale to 0..1.0
    val double y = Float::parseFloat(xyY.split(",").get(1).substring(1)) / 100.0 // scale to 0..1.0
    //val int   Y = (Float::parseFloat(xyY.split(",").get(2).substring(1).split("]").get(0)) * 2.55).intValue !! not useful, use HSBType brightness instead
    val int   Y = (hsbValue.brightness * 2.55).intValue // scale to 0..255

    logInfo("deconz", "xyY=" + xyY.toString)
    logInfo("deconz", "x=" + x)
    logInfo("deconz", "y=" + y)
    logInfo("deconz", "Y=" + Y)

    val String xyString = '{"xy": [' + x.toString + ',' + y.toString + ']}'
    val String YString = '{"bri":' + Y.toString + '}'

    logInfo("deconz", "xyString=" + xyString)
    logInfo("deconz", "YString=" + YString)

    logInfo("deconz", "Trumpet4PolledBri=" + Trumpet4PolledBri.state)

    deconzUrlString = deconzHost + lightTrumpet4
    if (Trumpet4PolledBri.state != Y.toString) {
      result = sendHttpPutRequest(deconzUrlString, "application/json", YString, 5000)
      logInfo("deconz", "sendHttpPutRequest() returned: " + result)
      Thread::sleep(2000)
    }
    result = sendHttpPutRequest(deconzUrlString, "application/json", xyString, 5000)
    logInfo("deconz", "sendHttpPutRequest() returned: " + result)    
end

log:

2018-08-04 20:55:35.546 [INFO ] [clipse.smarthome.model.script.deconz] - vBulbTrumpet4Color=353,97,100
2018-08-04 20:55:35.547 [INFO ] [clipse.smarthome.model.script.deconz] - xyY=[62.32024, 32.190536, 21.498209]
2018-08-04 20:55:35.548 [INFO ] [clipse.smarthome.model.script.deconz] - x=0.6232024002075195
2018-08-04 20:55:35.548 [INFO ] [clipse.smarthome.model.script.deconz] - y=0.32190536499023437
2018-08-04 20:55:35.548 [INFO ] [clipse.smarthome.model.script.deconz] - Y=255
2018-08-04 20:55:35.549 [INFO ] [clipse.smarthome.model.script.deconz] - xyString={"xy": [0.6232024002075195,0.32190536499023437]}
2018-08-04 20:55:35.549 [INFO ] [clipse.smarthome.model.script.deconz] - YString={"bri":255}
2018-08-04 20:55:35.550 [INFO ] [clipse.smarthome.model.script.deconz] - Trumpet4PolledBri=1
2018-08-04 20:55:35.552 [INFO ] [clipse.smarthome.model.script.deconz] - sendHttpPutRequest() returned: [{"success":{"/lights/30/state/bri":255}}]
2018-08-04 20:55:37.555 [INFO ] [clipse.smarthome.model.script.deconz] - sendHttpPutRequest() returned: [{"success":{"/lights/30/state/xy":[0.623202,0.321905]}}]

Please feel free to suggest improvements as I really struggled with picking the vales from the returned percentType[]

Anyway, works like a charm, but the bulb must not get the bri and xy settings too close in time.

Cheers.

Apologies in advance for the thread necro.

I tried this method and while it works the colors aren’t perfectly correct. For example when I try giving it red HSB of 0,100,100 I get an x=0.64 and y=0.33, when the correct “pure” red color would be closer to x=0.74 and y=0.28.

I’m right now trying to figure out how to solve this but decided to also check if you ran into this issue and already solved it yourself.

No, I was happy just getting my party light mode going. Never did any color calibrating or comparisons with other lights.
Maybe @chris who wrote the transform has?

I didn’t write this either - sorry. I just moved it into the common class as before that it was duplicated in multiple places.

I found the issue, and it might not be happening to your bulbs. The HSB to XY conversion in the code does HSB to sRGB to CIE 1931 XY using D65 white. Their formula is meant to calculate values as if they’re the inside triangle you see on this plane.
image

The issue is my bulbs take in reference to a triangle where the corners align with the limits of Red, Green and Blue on the whole plane, not just that internal triangle outlined on the pic. In order to fix this, I need to work on some math to be able to convert the coordinates from their XY to the one my bulbs use, using 0.33,0.33 white as the center.

I think this is doable and I have some Math attempts on paper, so once I get it done I’ll post back with the results.

I posted here again in case someone else runs into this thread with the same issue. When I was searching for a solution this thread kept constantly popping up for me, so I suspect it would for others with similar issues as well.

1 Like

Hey @jluzonpr ,
have you had time to work on this? I do have the same Issue and no clue how to mathematically fix this…
Cheers

Hi there,

i had have the same issue with my Innr RB 250 C, which also offers no HSB Commands over zigbee2mqtt, so i had to transform the colors. As stated, the problem is the restriction to the sRGB Colorspace (the triangle in the Picture) in the colortransformation of the HSBType. Alternative solution:

Use the RGB Values from the HSBType (hsb.getRGB()) and handle them as Wide RGB Colorspace. Then transform them to XYZ by your own using the Wide RGB Matrix from here (notice, that’s JS code, so you have to transform it to your programming language):

//sR, sG and sB (Standard RGB) input range = 0 ÷ 255
//X, Y and Z output refer to a D65/2° standard illuminant.
var sR = msg.payload.r;
var sG = msg.payload.g;
var sB = msg.payload.b;

var var_R = ( sR / 255 );
var var_G = ( sG / 255 );
var var_B = ( sB / 255 );

var_R = Math.pow(var_R, 2.19921875);
var_G = Math.pow(var_G, 2.19921875);
var_B = Math.pow(var_B, 2.19921875);

var_R = var_R * 100;
var_G = var_G * 100;
var_B = var_B * 100;

X = var_R * 0.7161046 + var_G * 0.1009296 + var_B * 0.1471858;
Y = var_R * 0.2581874 + var_G * 0.7249378 + var_B * 0.0168748;
Z = var_R * 0.0000000 + var_G * 0.0517813 + var_B * 0.7734287;

To get the x,y values you finally have to calculate them with

x =X/(X+Y+Z);
y =Y/(X+Y+Z);

Works as a charme :slight_smile:

@Kai mybe its possible, to switch vom sRGB matrix to wRGB matrix within the HSBType, or even better, to offer different colorspaces in the conversion function. I think most x,y capable Bulbs are able to display lot more then sRBG, especially the difference is huge in green an noticalbe in red.

@trisomeyr Thank you, this looks so much better now! Red is actually Red :partying_face:

Below my first draft of translation to an OpenHab Rule.

I found one Issue with your example: If you set the Brightness to 0 in the Color Dialog at the Sitemap of OH, you divide by zero, as X+Y+Z==0. In case of division with 0 I will only update brightness and not the color.

About the items:
IKEA_Lampe_2_Farbe_Intern > The color value manipulated by the sitemap
IKEA_Lampe_2_Farbe > The x y string send on zigbee
IKEA_Lampe_2_Dimmer > the brightness value from 0 to 255

rule "IKEA Lampe 2 - Color"
    when
        Item IKEA_Lampe_2_Farbe_Intern changed
    then
        //https://community.openhab.org/t/solved-convert-hsbtype-to-cie-xy-needed-for-ikea-tradfri-control-through-deconz-rest/48825/9
        var HSBType hsbValue = IKEA_Lampe_2_Farbe_Intern.state as HSBType

        //sR, sG and sB (Standard RGB) input range = 0 ÷ 255
        //X, Y and Z output refer to a D65/2° standard illuminant.
        var sR = ((IKEA_Lampe_2_Farbe_Intern.state as HSBType).red * 2.55).floatValue;
        var sG = ((IKEA_Lampe_2_Farbe_Intern.state as HSBType).green * 2.55).floatValue;
        var sB = ((IKEA_Lampe_2_Farbe_Intern.state as HSBType).blue * 2.55).floatValue;

        var Number var_R = ( sR / 255 ).floatValue;
        var Number var_G = ( sG / 255 ).floatValue;
        var Number var_B = ( sB / 255 ).floatValue;

        var_R = Math.pow(var_R, 2.19921875);
        var_G = Math.pow(var_G, 2.19921875);
        var_B = Math.pow(var_B, 2.19921875);

        var_R = var_R * 100;
        var_G = var_G * 100;
        var_B = var_B * 100;

        var X = var_R * 0.7161046 + var_G * 0.1009296 + var_B * 0.1471858;
        var Y = var_R * 0.2581874 + var_G * 0.7249378 + var_B * 0.0168748;
        var Z = var_R * 0.0000000 + var_G * 0.0517813 + var_B * 0.7734287;

        if((X+Y+Z) > 0){
            var x =X/(X+Y+Z);
            var y =Y/(X+Y+Z);        
            var String text="{\"x\":" + String.format("%.3f", x) + ",\"y\":" + String.format("%.3f", y) + "}"
            IKEA_Lampe_2_Farbe.sendCommand(text)
        }

        val Brightness = hsbValue.brightness.intValue * 2.55
        IKEA_Lampe_2_Dimmer.sendCommand(String.format("%.0f",Brightness))
    end

Super dope man. I had solved it before by hardcoding the colors I wanted and using a mapping but decided to give what you did a try. I ended up turning it into a JS Transform so I wouldn’t need a rule per bulb in the future, or to keep adding new bulbs to the same rule. This is pretty much your exact code turned into a JS Transform and some small changes from my own coding style.

(function(i) {
var sRGB = input.split(',');
//sR, sG and sB (Standard RGB) input range = 0 ÷ 255
//X, Y and Z output refer to a D65/2° standard illuminant.
var sR = sRGB[0];
var sG = sRGB[1];
var sB = sRGB[2];

var var_R = ( sR / 255 );
var var_G = ( sG / 255 );
var var_B = ( sB / 255 );

var_R = Math.pow(var_R, 2.19921875);
var_G = Math.pow(var_G, 2.19921875);
var_B = Math.pow(var_B, 2.19921875);

var_R = var_R * 100;
var_G = var_G * 100;
var_B = var_B * 100;

var X = var_R * 0.7161046 + var_G * 0.1009296 + var_B * 0.1471858;
var Y = var_R * 0.2581874 + var_G * 0.7249378 + var_B * 0.0168748;
var Z = var_R * 0.0000000 + var_G * 0.0517813 + var_B * 0.7734287;

var x = X/(X+Y+Z);
var y = Y/(X+Y+Z);

var payload = {};
payload.color = {};
payload.color.x = x;
payload.color.y = y;

return JSON.stringify(payload);

})(input)

I just saved it under bulbColorFix.js in my transform folder.

This is what a thing looks like for me with the transform:

Type colorRGB : color "Color"  [ commandTopic="zigbee2mqtt/bedroom_lights/set",
stateTopic="zigbee2mqtt/bedroom_lights", transformationPatternOut="JS:bulbColorFix.js"]

For a complete solution I’d also need a reverse function to turn back XYZ into sRGB but I don’t think it’s worth it. I’m not sure what the impact is for not having OH get the feedback from the color being set, but if someone can come up with a good reason I’ll take a crack at it.

1 Like

I’ve taken a crack at reversing this algorithm to get sRGB from CIE xy but I’m not good at matrix math and TBH I think a problem is that that converting from XYZ to xy is lossy. Do you have any idea how to go about reversing this process?

Thank you so much! Id did the trick!