codetheweb / tuyapi

🌧 An easy-to-use API for devices that use Tuya's cloud services. Documentation: https://codetheweb.github.io/tuyapi.
MIT License
2.08k stars 342 forks source link

Tuya light color encoding to rgb #346

Closed Gringoniceguy closed 4 years ago

Gringoniceguy commented 4 years ago

I am having a issue with setting the color of my light bulb using the api. The color seems to be encoded in a non standard format pretty sure it is this format: https://github.com/arendst/Tasmota/issues/8880. i have no idea how to convert this into rbg but i think this is possible as with tasmota you can control the color of the bulb

codetheweb commented 4 years ago

If you search all issues on this repo, RGB encoding has been discussed before.

You may find this implementation helpful.

BunnCode commented 3 years ago

//github.com/arendst/Tasmota/issues/8880).

i have no idea how to convert this into rbg but i think this is possible as with tasmota you can control the color of the bulb

In case you're still having this problem, I just figured it out. The schema is two bytes each of R, G, B, followed by followed by four bytes of hue (in the range 0 to 360) followed by two bytes of value, followed by two bytes which represent the brightness.

All values except for hue are 0-255, and all values must be set.

To recap: RR GG BB HHHH VV BrBr

So, for soft pink (127, 0, 0) at 100% brightness, you would call

  await device.set({
    multiple: true,
    data: {
      1  : true,
      2  : "colour",
      5  : "800000000080ff"
    },
  });

Pretty confusing setup! Not sure why they did it like this, but it seems like the schema is the same for all of the generic chinese RGB/RGBWT bulbs that follow the layout.

corey-mitchell commented 3 years ago

@ChunkBuster Couple of questions about this, can you elaborate on the two bytes of 'value'? What 'value' exactly are we choosing/setting here? I understand the hex for R, G, B, Hue and Brightness but what is this extra value actually for? Saturation perhaps?

Also, any links you could send me about where you found this in the documentation would be wonderful, because I'm still going through and haven't been able to find this section. Nor have I been able to find out what each of the numbers in the .set() method's 'data' object is suppose coincide with. I ask because my data stream displays numbers higher than the documentation but with the same expected values. i.e. 20-26 instead of 1-5.

Thanks for the help!

Edit: So to answer my own question for any future people who end up here, 'value' is from HSV (hue, saturation and value). You can search HSV for more information on that to find out what 'value' is.

As for links to documentation, I could only find a python module that documented some of the keys and what their coinciding values are. That documentation is here: https://pypi.org/project/tinytuya/. You can find tables with most of the desired values about 3/4 down that page.

To answer why my values are 20-26 and not 1-5, is because my firmware version is 3.3 rather than 3.1 which has different keys and values (I suspect because they are different types of lights. Mine is a dynamic light that allows motion and multiple colors and I believe 3.1 is only for static single color lighting).

For anyone else who may have 3.3 and be confused as to how it works I have compiled some notes for how everything works. (This is compiled from some of the linked tables and me switching things up in the app while monitoring a status stream.)

20 is on/off.

21 is color mode. The available modes are: white, colour, static, pre_set, custom and local_music.

22 is brightness. This is an integer between 10 and 1000. But, this only controls the brightness on the 'white' mode. If you set brightness this way, it will default to the 'white' mode.

23 is color temp/lightness. This in an integer between 0 and 1000. Likewise to brightness, however, this only works on the 'white' mode and will default to the 'white' mode if set.

24 is for setting a single color. This will be a hex string in HSV units. Each unit will be four hex digits, for a total of a 12 digit hex string. Similar to the brightness and lightness, this option is only for the 'colour' mode and if you set this value it will default to the 'colour' mode. (There is an npm package called 'color-convert' that is perfect for converting RGB values to HSV and vice versa. It can also convert RGB and HSV to Hex, but because of the required format I would recommend writing your own function that will pad the Hex with leading zeros which you can check below to see what I did.)

_converDecToHex(decimal, chars) { // This converts a number to a string Hex AND adds padding zeros to a set number of characters.
    return (decimal + Math.pow(16, chars)).toString(16).slice(-chars);
} ;

_convertRgbToHex(rgb) { // This converts a given RGB array into the proper HSV then returns the required 12 digit Hex string.
    let hsv = convert.rgb.hsv(rgb); // Convert RGB to HSV
    let hex = hsv.map((data, index) => { // Convert HSV to Hex
        if(index === 0) { // If Hue value, 
            return this._convertDecToHex(data, 4); // Convert value to Hex
        else { // Else,
            return this._convertDecToHex(data * 10, 4); // Multiply value by 10 because the given value is in a percentage, 0 - 100,  which is not the required value range, 0 - 1000.
        };
    });
    let hsvHex = hex.join(''); // Join value into string
    return hsvHex; // Return hex string in required format
};

From there, you can just pass the hex string to the device like so:

setSingleColor(rgb) {
    let hex = this._convertRgbToHex(rgb); // Convert RGB to required Hex format
    this.device.set({dps: '24', set: hex}).then(do stuff).catch(catch errors); // Set color
};

25 is for setting a 'scene'. This one is quite complex, as it contains the majority of the desired features, and required a lot of trial and error but I'm going to do my best to explain everything as I currently understand it.

So, to sum this all up: 4 digits of brightness 2 digits of Speed 1 Buffer Digit 1 Play/Pause Digit 1 Buffer Digit BrBrBrBr SpSp Bf Pl Bf

Shorthand: BrBrBrBr SpSp Bf Pl Bf [Append up to eight 12 digit hex strings here for each desired color]

Code sample for a 'static' mode scene:

setStaticScene(colors=[]) { // Takes in an array of RGB color arrays and makes a static scene from them. All the other values are hard coded, but they could just as easily be made into parameters for dynamic control.
    let brightness = this._convertDecToHex(100 * 10, 4); // 100% brightness
    let motionSpeed = this._convertDecToHex(0, 2); // No motion speed (because we're in 'static' mode for this example)
    let motionPause = 2; // This value doesn't matter for static scenes so it doesn't really matter what you put here, but default is 2.
    let colorHex = colors.map(color => this._convertRgbToHex(color)); // Make a new array of HSV values from the given RGB values
    let hexArr = [brightness, motionSpeed, 0, motionPause, 0, ...colorHex]; // Make an array of all the values in their required places. Note that the zeros are for the buffer bits/digits.

    this.device.set({dps: '25', set: hexArr.join('')}).then(do stuff).catch(catch errors); // Set scene to joined Hex string
};

Whew... That is a lot to take in so you will probably need some trial and error as well to fully grasp it.

Now, my light also had a couple of other values that are pretty simple so I'll just add those here as well.

103 is for setting some sort of configuration units. (They are literally just called 'units' and have no explanation.) But, from what I've observed, they seem to adjust the light flow a bit. So the value range is from 1 - 150. Anything under 100 will just light up sections of the light for me instead of the entire light. Whereas values above 100 seem to change where the colors blend.

104 is for selecting the preset options that come with your device. They range from 0 - 11 for me, as there are 12 presets for the 'static', 'pre_set', 'custom' and 'local_music' modes. (There are no presets for the 'white' or 'colour' modes so this number will always be 0, even if you set it to something else.)

That's everything I've compiled so far. Hopefully it helps someone out and let me know if you have any questions or comments!

Frontesque commented 1 year ago

ABOUT

Alright. For anyone looking for a quick "plug and play" solution, I worked with my friend @spliicxr to port the tinytuya function that controls the lights via RGB input to javascript.

The specs are basically what @BunnCode had detailed in her previous post in this thread (thanks for the help)

THE FUNCTIONS

So without further ado, here is a minified version of the functions that you can throw into your code.

function rgb2hsv(t,e,n){t/=255,e/=255,n/=255;var r,a,s=Math.max(t,e,n),g=Math.min(t,e,n),i=s,l=s-g;if(a=0==s?0:l/s,s==g)r=0;else{switch(s){case t:r=(e-n)/l+(e<n?6:0);break;case e:r=(n-t)/l+2;break;case n:r=(t-e)/l+4;break}r/=6}return[r,a,i]}
function rgb2tuya(t,e,n){const r=[t,e,n],a=rgb2hsv(r[0]/255,r[1]/255,r[2]/255);let s=new String,g=new String;for(const t in r){let e=r[t].toString(16);1==e.length&&(e="0"+e),s+=e}let i=[parseInt(360*a[0]),parseInt(255*a[1]),parseInt(255*a[2])];for(const t in i){let e=i[t].toString(16);1==e.length&&(e="0"+e),g+=e}return 7==g.length&&(g="0"+g),6==g.length&&(g="00"+g),s+g.slice(0,-2)+"ff"};

UNMINIFIED

For a file that you can import (and the unminified code), you can check out my gist, here. I just didn't want to throw all of that into this thread. I hope you all understand. Any questions or problems with the function should be posted as replies to the thread and not here. This shouldn't be the tuyapi dev's problem as this isn't his function.

Hopefully he will clean it up a bit and add it natively some day.

USAGE

await device.set({
    multiple: true,
    data: {
      1: true,
      2: "colour",
      5: rgb2tuya(0, 100, 255) // 0 being Red, 100 being Green, 255 being Blue.
    }
  });
b0r1ngx commented 7 months ago

I just figured it out.

For anyone else who may have 3.3 and be confused as to how it works I have compiled some notes for how everything works.

@BunnCode , @corey-mitchell Thanks a lot for this information! It's so helpful! Still in 2024 Tuya SDK/API docs is a bullshit.