Koenkk / zigbee2mqtt

Zigbee 🐝 to MQTT bridge 🌉, get rid of your proprietary Zigbee bridges 🔨
https://www.zigbee2mqtt.io
GNU General Public License v3.0
11.98k stars 1.67k forks source link

Color conversions #3497

Closed pgScorpio closed 4 years ago

pgScorpio commented 4 years ago

Bug Report

What happened

When setting the device color, i.e. for GLEDOPTO GL-C-007, you can now set the device color in rgb format, but the reported color in the status report is still always in xy.

Is there any support for converting the reported xy values back to RGB ?

I've tried several conversion methods found on the internet, but not a single one of them converts back to the same RGB values that where send in the command (most of them even result in some negative RGB values). Actually this even can be expected since the xy color space is normally device dependant and can only be converted if you know the correct xy coordinates for R,G and B (and used Y value).

What did you expect to happen

Reported color in the same format (and values ;=)) as issued by the (latest) command.

How to reproduce it (minimal and precise)

Just subscribe to device status and send a set color command in RGB format, than look at the reported state (color in xy format).

Debug Info

Zigbee2mqtt version: 1.12.2 Adapter hardware: CC2530 Adapter firmware version: zStack12, dateCode: 20190608 GLEDOPTO GL-C-007, HardwareVersion: 1, softwareBuildID: 2.0.3 , dateCode: 20180920

sjorge commented 4 years ago
function xy2rgb(x, y, brightness){
    //Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons)
    if (brightness === undefined) {
        brightness = 254;
    }

    var z = 1.0 - x - y;
    var Y = (brightness / 254).toFixed(2);
    var X = (Y / y) * x;
    var Z = (Y / y) * z;

    //Convert to RGB using Wide RGB D65 conversion
    var red     =  X * 1.656492 - Y * 0.354851 - Z * 0.255038;
    var green   = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
    var blue    =  X * 0.051713 - Y * 0.121364 + Z * 1.011530;

    //If red, green or blue is larger than 1.0 set it back to the maximum of 1.0
    if (red > blue && red > green && red > 1.0) {

        green = green / red;
        blue = blue / red;
        red = 1.0;
    }
    else if (green > blue && green > red && green > 1.0) {

        red = red / green;
        blue = blue / green;
        green = 1.0;
    }
    else if (blue > red && blue > green && blue > 1.0) {

        red = red / blue;
        green = green / blue;
        blue = 1.0;
    }

    //Reverse gamma correction
    red     = red <= 0.0031308 ? 12.92 * red : (1.0 + 0.055) * Math.pow(red, (1.0 / 2.4)) - 0.055;
    green   = green <= 0.0031308 ? 12.92 * green : (1.0 + 0.055) * Math.pow(green, (1.0 / 2.4)) - 0.055;
    blue    = blue <= 0.0031308 ? 12.92 * blue : (1.0 + 0.055) * Math.pow(blue, (1.0 / 2.4)) - 0.055;

    //Convert normalized decimal to decimal
    red     = Math.round(red * 255);
    green   = Math.round(green * 255);
    blue    = Math.round(blue * 255);

    if (isNaN(red))
        red = 0;

    if (isNaN(green))
        green = 0;

    if (isNaN(blue))
        blue = 0;

    return {
        r: red,
        g: green,
        b: blue,
    };
}

Should work, I use it as part of my homekit flow in nodered (well I have xy2rgb, rgb2xy, rgb2hsv, and hsv2rgb becauseh omekit is hsv (with a fixed v=100)

But the results of xy2rgb was able to reverse the z2m result for me.

pgScorpio commented 4 years ago

Hi Jorge (Schrauwen klinkt erg nederlands ?),

Thanks very much ! Works better than anything I had until now, but still not perfect, especially near saturated colors. (See Node red log)

Greetz, Peter Goderie

Node red log:

5/7/2020, 1:58:26 PMnode: Item to Zigbee Controlhttp://192.168.19.200:1880/#cmnd/Lights/Huiskamer/Ledstrip/:color : msg : Object object topic: "cmnd/Lights/Huiskamer/Ledstrip/:color" payload: "{"r":0,"g":250,"b":0,"a":1}" <== set RGB 0,250,0 qos: 0 retain: false _topic: "cmnd/Lights/Huiskamer/Ledstrip/:color" _msgid: "ee3f4c78.2ef77" item: object _event: "node:2d4c842e.6eab8c" 5/7/2020, 1:58:26 PMnode: zigbee Color Status msghttp://192.168.19.200:1880/#stat/Lights/Huiskamer/Ledstrip/:color : msg : Object object topic: "stat/Lights/Huiskamer/Ledstrip/:color" payload: object qos: 0 retain: false _topic: "postfixed/zigbee/Ledstrip" _msgid: "c2501e93.673f8" item: object source: "zigbee" dest: "" name_topic: "stat/Lights/Huiskamer/Ledstrip" value_topic: "stat/Lights/Huiskamer/Ledstrip/:color" prefix: "stat" postfix: "" cmd: "stat" type: "Lights" location: "Huiskamer" name: "Ledstrip" value_id: "color" value_type: "object" value: object <== xy2rgb(returned X,Y) -> RGB 0,255,0 !! r: 0 g: 255 b: 0 _event: "node:be80e3a4.8633d" http://192.168.19.200:1880/#


Van: Jorge Schrauwen notifications@github.com Verzonden: dinsdag 5 mei 2020 09:02 Aan: Koenkk/zigbee2mqtt zigbee2mqtt@noreply.github.com CC: pgScorpio pg_Scorpio@hotmail.com; Author author@noreply.github.com Onderwerp: Re: [Koenkk/zigbee2mqtt] Color conversions (#3497)

function xy2rgb(x, y, brightness){ //Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons) if (brightness === undefined) { brightness = 254; }

    var z = 1.0 - x - y;
    var Y = (brightness / 254).toFixed(2);
    var X = (Y / y) * x;
    var Z = (Y / y) * z;

    //Convert to RGB using Wide RGB D65 conversion
    var red         =  X * 1.656492 - Y * 0.354851 - Z * 0.255038;
    var green       = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
    var blue        =  X * 0.051713 - Y * 0.121364 + Z * 1.011530;

    //If red, green or blue is larger than 1.0 set it back to the maximum of 1.0
    if (red > blue && red > green && red > 1.0) {

            green = green / red;
            blue = blue / red;
            red = 1.0;
    }
    else if (green > blue && green > red && green > 1.0) {

            red = red / green;
            blue = blue / green;
            green = 1.0;
    }
    else if (blue > red && blue > green && blue > 1.0) {

            red = red / blue;
            green = green / blue;
            blue = 1.0;
    }

    //Reverse gamma correction
    red     = red <= 0.0031308 ? 12.92 * red : (1.0 + 0.055) * Math.pow(red, (1.0 / 2.4)) - 0.055;
    green   = green <= 0.0031308 ? 12.92 * green : (1.0 + 0.055) * Math.pow(green, (1.0 / 2.4)) - 0.055;
    blue    = blue <= 0.0031308 ? 12.92 * blue : (1.0 + 0.055) * Math.pow(blue, (1.0 / 2.4)) - 0.055;

    //Convert normalized decimal to decimal
    red     = Math.round(red * 255);
    green   = Math.round(green * 255);
    blue    = Math.round(blue * 255);

    if (isNaN(red))
            red = 0;

    if (isNaN(green))
            green = 0;

    if (isNaN(blue))
            blue = 0;

    return {
    r: red,
    g: green,
    b: blue,
};

}

Should work, I use it as part of my homekit flow in nodered (well I have xy2rgb, rgb2xy, rgb2hsv, and hsv2rgb becauseh omekit is hsv (with a fixed v=100)

But the results of xy2rgb was able to reverse the z2m result for me.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/Koenkk/zigbee2mqtt/issues/3497#issuecomment-623888389, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ACGLHX2FFF36A6OSUVCCNWDRP62ZPANCNFSM4MZDKLEQ.

pgScorpio commented 4 years ago

I did some further investigation and this xy2rgb.

The problem is that the send RGB values should be 100% brightness (so at least one value must be 255). Though the examples at https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html do NOT show this ! This would imply that zigbee2mqtt should adjust brightness from rgb values too !?

My problem is that the node-red colour picker does not support full brightness rgb colour only (nor x,y colour). But when correcting the RGB values 100% brightness before sending the xy2rgb converted returned result seems always the same as the send value.

Nevertheless I still think that sending a RGB value to zigbee2mqtt should also return a RGB value, or it should return all accepted colour formats: color: {"x":0.4672,"y":0.3166,"h":217.9999,"s":0.8,"v":1, "r":255,"g":156,"b":157,...} or even better in the same format as sent: color: {"xy":{"x":0.4672,"y":0.3166},"hsv":{"h":217.9999,"s":0.8,"v":1},"rgb":{"r":255,"g":156,"b":157},"hex":"#FF9C9D",...}

sjorge commented 4 years ago

It just returns xy or hue/sat because that is what the bulb returns. There is no conversion there.

It also always sends xy or hue/sat to the bulb. I guess ideally all the conversions should be left out of the app. I also have a rgb2xy that I use to send xy... I can share that one when I am at a computer.

~ sjorge

On 7 May 2020, at 19:10, pgScorpio notifications@github.com wrote:

 I did some further investigation and this xy2rgb.

The problem is that the send RGB values should be 100% brightness (so at least one value must be 255). Though the examples at https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html do NOT show this ! This would imply that zigbee2mqtt should adjust brightness from rgb values too !?

My problem is that the node-red colour picker does not support full brightness rgb colour only (nor x,y colour). But when correcting the RGB values 100% brightness before sending the xy2rgb converted returned result seems always the same as the send value.

Nevertheless I still think that sending a RGB value to zigbee2mqtt should also return a RGB value, or it should return all accepted colour formats: color: {"x":0.4672,"y":0.3166,"h":217.9999,"s":0.8,"v":1, "r":255,"g":156,"b":157,...} or even better in the same format as sent: color: {"xy":{"x":0.4672,"y":0.3166},"hsv":{"h":217.9999,"s":0.8,"v":1},"rgb":{"r":255,"g":156,"b":157},"hex":"#FF9C9D",...}

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

sjorge commented 4 years ago
function rgb2xy(red, green, blue) {
    // The RGB values should be between 0 and 1. So convert them.
    // The RGB color (255, 0, 100) becomes (1.0, 0.0, 0.39)
    red /= 255; green /= 255; blue /= 255;

    // Apply a gamma correction to the RGB values, which makes the color
    // more vivid and more the like the color displayed on the screen of your device
    red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
    green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
    blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);

    // RGB values to XYZ using the Wide RGB D65 conversion formula
    const X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
    const Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
    const Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;

    // Calculate the xy values from the XYZ values
    let x = (X / (X + Y + Z)).toFixed(4);
    let y = (Y / (X + Y + Z)).toFixed(4);

    if (isNaN(x)) {
        x = 0;
    }

    if (isNaN(y)) {
        y = 0;
    }

    return {x: Number.parseFloat(x), y: Number.parseFloat(y)};
}

I think it is nearly the same as the one z2m uses though.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

MattL0 commented 1 year ago

Hi, it would be great to get the hex value back instead of x:y.

Would it be possible to add an option to always send coor as hex instead of x:y ? @Koenkk

Koenkk commented 1 year ago

@MattL0 please open a feature request, then we can see how many users are interested in this.