iRayanKhan / homebridge-tuya

A Homebridge plugin to control Tuya devices locally.
MIT License
380 stars 160 forks source link

RGBTW Color Temp Range incorrectly reported to HomeKit as 0-600 despite configuration. #357

Open ekobres opened 2 years ago

ekobres commented 2 years ago

Checklist

Describe the bug HomeKit thinks the range of Tuya RGBTW bulbs is 0-600 mired, regardless of the configured min/max, which is incorrect. The configured min/max in the accessory definition should be passed to HomeKit. For example, Wiz-LAN plugin does this properly. Also, HK certified bulbs such as Hue, Meross and Nanoleaf have a color temp range that matches the actual characteristics of the bulb. The bulbs I am using are Feit WiFi Color with Tunable White and their range is 154-370 (6500K-2700K.) These bulbs are HSB with white temperature scale of 1000.

Although you can reach the min and max colors of the bulb (0-1000 for example with an HSB bulb with 1000 scale) with the temperature picker, the reported mired level HomeKit sees is way off - and ranges from 71 to 600. This is a problem if you want to try to match other bulbs in the room - or to use any of the "White" color temperatures Siri understands, like Topaz, Sandy Brown, etc.

To Reproduce Set up a Tuya LAN bulb with HSB color values and a 1000 color temperature scale. Set up the Minimum and Maximum white temperature values to be correct for the bulb - or use the defaults of 140 & 400. Set the bulb to the warmest possible color using the Tuya app. Use an app such as Controller for HomeKit that can read the properties of accessories and examine the min/max color temperature properties of the Tuya plugin bulb. The minimum value will be 0, and the Max will be 600. Likewise, if you set the bulb through the Tuya or other app to the highest mired value - HomeKit will show that the bulb is set to 600 mired, even though the actual mired value is probably in the 370-400 range. (Some, like Hue bulbs go all the way to 2000K or 500 mired - but no real bulb goes to 600 that I have seen.)

Expected behavior Homekit minimum, maximum and current mired values for RGBTW HSB bulbs should be scaled based on the configured color range of the bulb, not based on a hard-coded range of 0-600 - which is the maximum range of HomeKit - not the bulb.

For example for an RGBTW light with HSB color commands, min color of 153, max color of 370, and color temperature scale of 1000, when the accessory Color Temperature property in HomeKit is set to the configured maximum white color (e.g. 370 mired), the command to the bulb should be the minimum scale value (e.g. 1). When the accessory Color Temperature property in HomeKit is set to the configured minimum white color (e.g. 153 mired), the command to the bulb should be the maximum scale value (e.g. 1000.)

Screenshots The screen shots are from the HomeKit Controller app - the bulb shown is Tuya RGBTW configured with a white range of 154-370 IMG_6609

This shot shows the manual input field for the color temp - and the app validates the possible range reported by HomeKit - which you see is 0-600. (Actually any value below 71 is invalid - but HomeKit seems to just pass through whatever the accessory properties are.) IMG_6610

This is a corresponding shot from a Hue Color Ambience bulb with a range of 153-500 mired. Notice that it correctly shows 369 mired while the bulb is set to 2700K - standard "Warm White" color. IMG_6611

And here you can see HomeKit is reporting the correct range for the Hue bulb: IMG_6612

Environment (please complete the following information):

Additional context Add any other context about the problem here.

ekobres commented 2 years ago

To add a bit - I think I see what's going on - but I don't know enough about the framework to figure out where to go with it. Basically there should be a handler for min_value() and max_value() on the public.hap.characteristic.color-temperature characteristic - and those value should be the min/max from the accessory configuration.

This means that the implementation of the HomeKit<->Tuya conversion functions can be simplified to something like:

convertColorTemperatureFromHomeKitToTuya(value) {
        const min = this.device.context.minWhiteColor || 140;
        const max = this.device.context.maxWhiteColor || 400;
        const scale = this.device.context.scaleWhiteColor || 255;
        const result = Math.round(Math.max(Math.min(scale-(scale-1)/(max-min)*(value-min),scale),1));
        return result;
    }

and

convertColorTemperatureFromTuyaToHomeKit(value) {
        const min = this.device.context.minWhiteColor || 140;
        const max = this.device.context.maxWhiteColor || 400;
        const scale = this.device.context.scaleWhiteColor || 255;
        const result = Math.round(Math.max(Math.min((scale-value)/((scale-1)/(max-min))+min,max),min));
        return Math.min(600, Math.max(71, result));
    }

With this code my light colors actually match the color temperature value now - but the color picker is still stuck in the 0-600 range. That's not a huge deal because at least now every part of the system has the correct mired values for the lights - and the conversion functions ensure the values stay within the light's actual range.

So, if anyone can point me in the direction of how to implement the min_value() and max_value() callbacks - I could probably fix it myself from there.

Thanks!

iRayanKhan commented 2 years ago

Thanks for the well documented issue, and code snippet. Did your code change resolve homebridge characteristic warnings? Hoping to get this patched in 2.1.0/3.0.0.

ekobres commented 2 years ago

The only time I saw those warnings was when I tried to set the min/max properties that are hard-coded 71, 600 to different values. I’m not sure what that underlying implementation is doing - but I do know the Wiz Lan plugin somehow does it the right way - but the code doesn’t resemble the tuya stuff. It’s based on some pilot class I didn’t have time to dig into.

So the code I put in shows a more reasonable value - and it snaps the property back to the configured min/max, only allowing the value to scale that range.

I think the Apple min/max handler is how it sets the hottest and coolest colors shown on the temperature chooser. Hugh bulbs show a wide range - nanoleaf a much narrower range - but tuya is showing the maximum possible - almost red to almost blue.

On Feb 11, 2022, at 12:44 PM, Rayan Khan @.***> wrote:

 Thanks for the well documented issue, and code snippet. Did your code change resolve homebridge characteristic warnings? Hoping to get this patched in 2.1.0/3.0.0.

— Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you authored the thread.

Ferbez commented 1 year ago

@ekobres, you're just a magician! I've been trying to work around this myself and your code really me saved a lot of time! About hard-coded values - in RGBTWLightAccessory.js (judging by the time passed you've probably already figured this yourself, but still):

const characteristicColorTemperature = service.getCharacteristic(Characteristic.ColorTemperature)
    .setProps({
        minValue: this.device.context.minWhiteColor,
        maxValue: this.device.context.maxWhiteColor
    })

And now color picker works perfectly. However, there's a few other hard-coded functions. For example, this needs to be changed in the same RGBTWLightAccessory.js:

getColorTemperature(callback) {
    if (this.device.state[this.dpMode] !== this.cmdWhite) return callback(null, this.device.context.minWhiteColor);
    callback(null, this.convertColorTemperatureFromTuyaToHomeKit(this.device.state[this.dpColorTemperature]));
}

_setHueSaturation(prop, callback) {
    ...
    const callbacks = this._pendingHueSaturation.callbacks;
    const callEachBack = err => {
        async.eachSeries(callbacks, (callback, next) => {
            try {
                callback(err);
            } catch (ex) {}
            next();
        }, () => {
            this.characteristicColorTemperature.updateValue(this.device.context.minWhiteColor);
        });
    };
    ...
}

_registerCharacteristics(dps) {
    ...
    const characteristicColorTemperature = service.getCharacteristic(Characteristic.ColorTemperature)
        .setProps({
            minValue: 0,
            maxValue: 600
        })
        .updateValue(dps[this.dpMode] === this.cmdWhite ? this.convertColorTemperatureFromTuyaToHomeKit(dps[this.dpColorTemperature]) : this.device.context.minWhiteColor)
        .on('get', this.getColorTemperature.bind(this))
        .on('set', this.setColorTemperature.bind(this));
    ...
    default:
        if (changes.hasOwnProperty(this.dpColor)) {
            const oldColor = this.convertColorFromTuyaToHomeKit(this.convertColorFromHomeKitToTuya({
                h: characteristicHue.value,
                s: characteristicSaturation.value,
                b: characteristicBrightness.value
            }));
            const newColor = this.convertColorFromTuyaToHomeKit(changes[this.dpColor]);

            if (oldColor.b !== newColor.b) characteristicBrightness.updateValue(newColor.b);
            if (oldColor.h !== newColor.h) characteristicHue.updateValue(newColor.h);

            if (oldColor.s !== newColor.s) characteristicSaturation.updateValue(newColor.h);

            if (characteristicColorTemperature.value !== this.device.context.minWhiteColor) characteristicColorTemperature.updateValue(this.device.context.minWhiteColor);

        } else if (changes[this.dpMode]) {
            if (characteristicColorTemperature.value !== this.device.context.minWhiteColor) characteristicColorTemperature.updateValue(this.device.context.minWhiteColor);
        }
    ...
}

I also don't think that it's good practice to report 0 hue and 0 saturation when in white mode - this causes color temperature picker to not work properly (picker works, but as soon as homekit grabs new bulb state it shows that bulb is white no matter what it's actual color is - even though color temperature is being reported correctly), here's new code for the same RGBTWLightAccessory.js to fix this behaviour:

getHue(callback) {
        if (this.device.state[this.dpMode] === this.cmdWhite) {
                const prepareHue = this.convertHomeKitColorTemperatureToHomeKitColor(this.convertColorTemperatureFromTuyaToHomeKit(this.device.state[this.dpColorTemperature]));
                callback(null, prepareHue.h);
        } else {
                callback(null, this.convertColorFromTuyaToHomeKit(this.device.state[this.dpColor]).h);
        }
}

getSaturation(callback) {
        if (this.device.state[this.dpMode] === this.cmdWhite) {
                const prepareSaturation = this.convertHomeKitColorTemperatureToHomeKitColor(this.convertColorTemperatureFromTuyaToHomeKit(this.device.state[this.dpColorTemperature]));
                callback(null, prepareSaturation.s);
        } else {
                callback(null, this.convertColorFromTuyaToHomeKit(this.device.state[this.dpColor]).s);
        }
}