Can anyone help with the tuya smart device node?

I've used the tuya-smart-device node, and following the instructions and the example flow managed to get everything linked up and can turn the bulb on and off, but I'm a bit lost as to how I can change the colour and brightness etc.
I've tried looking at the tuyapi documentation but really don't understand any of it! I think I need to do something with the dps values, but don't know what they are or where I might find them.

This is a good place for Tuya device info tuya-mqtt/DEVICES.md at master · TheAgentK/tuya-mqtt · GitHub

I found it useful to connect the bulb to Smart Life because then, with a debug node connected to the Tuya node, I could determine what dps number controlled what aspect of the bulb.

Mine looked like this;

DPS Number     Command              Values                   Equates To
20             State                true or false            On or Off
21             Mode                 white, colour, scene, music
22             Brightness			10 - 1000	             1 - 100%*10
23             Colour Temperature	0 - 1000	             0 - 100% *10 [~2200K - 6500K] 
24             Colour Data HSV		Hue 0 - 360, 
                                    Saturation 1 - 1000 
                                    Value 0 - 1000           Saturation & Value / 10
26             Time out Value       0 - 86400                Seconds

My bulbs require the Colour Data to be in a hex format. For example a redish colour has the following HSV values

hue = 357
saturation = 100
value = 100

which gives a Tuya value of '016503e803e8'

To change colour you also have to change to Mode, like so
msg.payload = { "multiple": true, "data": { 21: 'colour', 24: '016503e803e8'}}

To change brightness
msg.payload = = {dps: 22, set: 800} Note that a selected Brightness of 1 - 100% has to be multiplied by 10 to give required Tuya value of 10 - 1000

I have a function to convert to and from HSV & Tuya values if you cannot find anything else.

Hope this helps point you in the right direction

That helps a lot, thank you! I can now set it to two different settings depending on another input, which is the important part. Now for the not so important part of seeing if I can make a dashboard control for it.

That would be very much appreciated if you can share it. It does seem like a rather odd way of defining the colour!

Here you go. There are probably better ways but this works for me

Notes:

  • The saturation & value adjustments are included.
  • The functions work with the newer style Tuya 12 character colour values)
  • Some explanations of Tuya colour mention HSB instead of HSV. As far as I can tell they are the same
	/******************************************************************************
	* Description	Converts an HSV colour value to Tuya hexadecimal colour.
	* 
	* 				Assumes Hue in set [0, 360], Saturation and Value in the set [0, 100] and
	* 				returns Tuya colour as 12 character hexadecimal string
	* 
	*
	* @param   {number | Object}     hue            - The hue or an Object. Example {357, 100, 100}
	* @param   {number}              saturation     - The saturation. Not required if Hue as Object
	* @param   {number}              value          - The value. Not required if Hue as Object
	* 
	* @returns {string}             - A 12 character hexadecimal string as used by Tuya devices for colour
	* 
	* Note: To convert RGB colour to Tuya colour use RGBtoHSV() as input
	*/

	function HSVtoTuyaColour(hue, saturation, value) {
		// Note: hue = hue.hue MUST go last
		if (arguments.length === 1) {
			saturation = hue.saturation;
			value = hue.value;
			hue = hue.hue;

		}

		// Increase saturation & value to correct set [0, 1000]
		saturation = saturation * 10;
		value = value * 10;

		return HSVtoHexStr(hue, saturation, value);

	} // End Function HSVtoTuyaColour()


	/******************************************************************************
	* Description	Converts a Tuya hexadecimal colour to an HSV colour .
	* 
	* 				Assumes Tuya colour hexadecimal representation: Hue in set [0, 360], Saturation and Value in the set [0, 1000]
	* 				returns an Object containing HSV hue, saturation and value
	* 
	*
	* @param   {string}	tuyaString	- Tuya colour as 12 character hexadecimal string
	* 
	* @returns {object}           	- The HSV representation of the tuya colour string
	* 
	* Note: To convert Tuya colour to RGB use HSVtoRGB()
	*/

	function TuyaColourToHSV(tuyaString) {
		let hue = hexToDecimal(tuyaString.slice(0, 4), 4);
		let saturation = hexToDecimal(tuyaString.slice(4, 8), 4) / 10;
		let value = hexToDecimal(tuyaString.slice(8, 12), 4) / 10;

		return { hue: hue, saturation: saturation, value: value };

	} // End Function TuyaColourToHSV()


	/******************************************************************************
	* Helper functions for HSVtoTuyaColour. Internal function only
	*/
	/******************************************************************************
	* Description	Converts an HSV colour value to hexadecimal string.
	* 
	* 				Assumes Hue in set [0, 360], Saturation and Value in the set [0, 100]
	* 				returns a 12 digit containing HSV hue, saturation and value
	* 
	*
	* @param   {number}	hue		       - HSV hue 
	* @param   {number}      saturation       - HSV saturation
	* @param   {number}      value	       - HSV value (brightness)
	* 
	* @returns {object}           	- The HSV representation
	* 
	* Note: To convert Tuya colour to RGB use HSVtoRGB()
	*/

	function HSVtoHexStr(hue, saturation, value) {
		let a = decimalToHex(hue, 4);
		let b = decimalToHex(saturation, 4);
		let c = decimalToHex(value, 4);

		return a + b + c;

	} // End Function HSVtoHexStr()

Thank you! I'll carry on playing with that tomorrow.

Sorry for probably being dumb, but I'm struggling to get this to work!

This was my original function that works with a switch and sliders for power, brightness, colour temp etc:

var topic = msg.topic
var payload = msg.payload

msg.payload = { 
    dps : topic,
    set : payload
}

return msg;

I've tried copying in your functions to make this:

var topic = msg.topic
var payload = msg.payload


msg.payload = { 
    dps : topic,
    set : payload
}

if (topic == "24") {
    payload = HSVtoTuyaColour();
}

return msg;

function HSVtoTuyaColour(hue, saturation, value) {
		// Note: hue = hue.hue MUST go last
		if (arguments.length === 1) {
			saturation = hue.saturation;
			value = hue.value;
			hue = hue.hue;

		}

		// Increase saturation & value to correct set [0, 1000]
		saturation = saturation * 10;
		value = value * 10;

		return HSVtoHexStr(hue, saturation, value);
}

function HSVtoHexStr(hue, saturation, value) {
		let a = decimalToHex(hue, 4);
		let b = decimalToHex(saturation, 4);
		let c = decimalToHex(value, 4);

		return a + b + c;

	}

But I'm getting the following error:
ReferenceError: decimalToHex is not defined

Am I missing another helper function for the decimalToHex, or am I just going about it all wrong?

The dashboard colour picker outputs either "hsv(112, 93%, 95%)" as a string, or "{"h":113,"s":0.9299999999999999,"v":0.95,"a":1}" as an object.
Do I also need to try and split that down to just the numerical values?

I also am trying to work out a spreadsheet to figure out how to get a slow fade ~30 seconds full brightness to off, makes my head hurt lol. Frustrating b/c the devices don't support a transition natively in home assistant so i'm trying to go the node-red route as the "scene" example here gives me a slow fade through all the colors

Many apologies I missed a couple of functions;

	/******************************************************************************
	* General Helper Functions
	* Also exposed as general conversion functions
	* 
	* 
	*/

	/** 	
  	*
  	* Description	Converts a decimal number to a Hex string
  	*				Removes any leading #. Checks that there are either 3 or 6 characters and tries to convert to a number
  	*               which results in NaN if false. Returns true or false
  	*
  	* @param 	{number}	decimal		- Decimal number       		Example 16777215
  	* @param 	{number}  	padCount    - Number of 0 used to pad Hex string with (defaults to each hex number having 2 characters. 
  	*
  	* @returns	{string} 		    	- Return a Hex string       Example 'FFF' becomes '0F0F0F'
  	*
  	*/

	function decimalToHex(decimal, padCount = 2) {
		return (decimal + Math.pow(16, padCount)).toString(16).slice(-padCount).toUpperCase();

	} // End Function decimalToHex()


	/**	
  	*
  	* Description	Converts a Hex string to a numeric
  	*				Tries to convert hex value to a number which results in NaN if false.
  	*
  	* @param	{String}	hexString   - Hex string					Example "#FFFFFF"
  	* @param	{Number}  	padCount    - Number of 0 used to pad Hex string with Example 'FFF' becomes '0F0F0F'
  	*
  	* @returns	{Number} 				- Return decimal number       Example 16777215
  	*
  	*/

	function hexToDecimal(hexString, padCount = 2) {
		if (isHexString(hexString)) {
			return parseInt(hexString.slice(-padCount), 16);

		} else {
			return undefined;

		}

	} // End Function hexToDecimal()

From the dashboard picker use the Object {"h":113,"s":0.9299999999999999,"v":0.95,"a":1}. (current value as an Object) The function call is then something like;

const colourValue = msg.payload

HSVtoTuyaColour(colourValue.h, colourValue.s * 100, colourValue.v * 100)

The "a" property can be ignored as not relevant to colour lights. s & v have to be multiplied by 100 to get them to % in range 0 - 100

To activate the example 'scene' you need to send;

const sceneValue = '010b0a02000003e803e8000000000b0a02007603e803e8000000000b0a0200e703e803e800000000'

msg.payload = { "multiple": true, "data": { 21: 'scene', 25: sceneValue } }

and to get back to 'white' just send

msg.payload = { 21: 'white' }

And thank you for pointing me at the 'scene' web page as previously I was struggling to determine how a scene string was constructed.

That's done it. Thank you!

I tried and tried for 14 days now, cant get it to work :frowning:
I selected the colour picker and tried to copy what you might have
but the results I get stay in h s v format :confused:

Could you be so friendly to share the function node that works for you pls?

Of course, here you go:

[{"id":"de52507a.9b7738","type":"tuya-smart-device","z":"4c5cecd1.d47ffc","deviceName":"SULION BULB","disableAutoStart":false,"deviceId":"bf54142ac5ada6f703xihw","deviceKey":"83974ba36c7c0db8","deviceIp":"","retryTimeout":1000,"findTimeout":1000,"tuyaVersion":"3.1","eventMode":"event-both","x":1120,"y":1680,"wires":[["28ee0b6a.449084"],["9153157.7519668"]]},{"id":"28ee0b6a.449084","type":"debug","z":"4c5cecd1.d47ffc","name":"Device Data","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1350,"y":1660,"wires":[]},{"id":"9153157.7519668","type":"debug","z":"4c5cecd1.d47ffc","name":"Node State","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1350,"y":1700,"wires":[]},{"id":"ea11f367.21a7","type":"ui_colour_picker","z":"4c5cecd1.d47ffc","name":"","label":"","group":"ae121250.1a901","format":"hsv","outformat":"object","showSwatch":false,"showPicker":true,"showValue":false,"showHue":false,"showAlpha":false,"showLightness":true,"square":"false","dynOutput":"false","order":12,"width":4,"height":4,"passthru":true,"topic":"24","topicType":"str","x":530,"y":1820,"wires":[["148174.ccb2d68c","f2beb67f.41e808"]]},{"id":"13ad14f7.e42283","type":"function","z":"4c5cecd1.d47ffc","name":"","func":"var topic = msg.topic\nvar payload = msg.payload\n\nmsg.payload = { \n    dps : topic,\n    set : payload\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":1740,"wires":[["de52507a.9b7738","941f3dae.28f508"]]},{"id":"d9a55dfd.20ebd8","type":"ui_switch","z":"4c5cecd1.d47ffc","name":"","label":"Power","tooltip":"","group":"ae121250.1a901","order":2,"width":4,"height":1,"passthru":true,"decouple":"false","topic":"20","topicType":"str","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"x":510,"y":1660,"wires":[["13ad14f7.e42283"]]},{"id":"9a1b8bcc.191fa8","type":"ui_dropdown","z":"4c5cecd1.d47ffc","name":"","label":"Wh / RGB","tooltip":"","place":"Select option","group":"ae121250.1a901","order":5,"width":4,"height":2,"passthru":true,"multiple":false,"options":[{"label":"","value":"white","type":"str"},{"label":"","value":"colour","type":"str"}],"payload":"","topic":"21","topicType":"str","x":520,"y":1700,"wires":[["13ad14f7.e42283"]]},{"id":"62a8bb81.35e964","type":"ui_slider","z":"4c5cecd1.d47ffc","name":"","label":"Brightness","tooltip":"","group":"ae121250.1a901","order":9,"width":2,"height":6,"passthru":true,"outs":"end","topic":"22","topicType":"str","min":"10","max":"1000","step":1,"x":530,"y":1740,"wires":[["13ad14f7.e42283"]]},{"id":"6c597fa0.fedfb8","type":"ui_slider","z":"4c5cecd1.d47ffc","name":"","label":"Warm/Cool","tooltip":"","group":"ae121250.1a901","order":10,"width":2,"height":6,"passthru":true,"outs":"end","topic":"23","topicType":"str","min":0,"max":"1000","step":1,"x":530,"y":1780,"wires":[["13ad14f7.e42283"]]},{"id":"941f3dae.28f508","type":"debug","z":"4c5cecd1.d47ffc","name":"msg passed to bulb","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1140,"y":1740,"wires":[]},{"id":"148174.ccb2d68c","type":"debug","z":"4c5cecd1.d47ffc","name":"msg from colour picker","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":790,"y":1860,"wires":[]},{"id":"f2beb67f.41e808","type":"function","z":"4c5cecd1.d47ffc","name":"","func":"const colourValue = msg.payload\n\nmsg.payload = HSVtoTuyaColour(colourValue.h, colourValue.s * 100, colourValue.v * 100)\n\nreturn msg;\n\nfunction HSVtoTuyaColour(hue, saturation, value) {\n\t\t// Note: hue = hue.hue MUST go last\n\t\tif (arguments.length === 1) {\n\t\t\tsaturation = hue.saturation;\n\t\t\tvalue = hue.value;\n\t\t\thue = hue.hue;\n\n\t\t}\n\n\t\t// Increase saturation & value to correct set [0, 1000]\n\t\tsaturation = saturation * 10;\n\t\tvalue = value * 10;\n\n\t\treturn HSVtoHexStr(hue, saturation, value);\n}\n\nfunction HSVtoHexStr(hue, saturation, value) {\n\t\tlet a = decimalToHex(hue, 4);\n\t\tlet b = decimalToHex(saturation, 4);\n\t\tlet c = decimalToHex(value, 4);\n\n\t\treturn a + b + c;\n\n\t}\n\t\nfunction decimalToHex(decimal, padCount = 2) {\n\t\treturn (decimal + Math.pow(16, padCount)).toString(16).slice(-padCount).toUpperCase();\n\n\t}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":1800,"wires":[["13ad14f7.e42283","1b4ca04b.24906"]]},{"id":"1b4ca04b.24906","type":"debug","z":"4c5cecd1.d47ffc","name":"msg from HSV conversion","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":970,"y":1800,"wires":[]},{"id":"ae121250.1a901","type":"ui_group","name":"Light control","tab":"5c1299a1.fcc84","order":7,"disp":true,"width":8,"collapse":false},{"id":"5c1299a1.fcc84","type":"ui_tab","name":"Final Call","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

From the colour picker, the first function node deals with the conversion from HSV to tuyas colour, and then the second function node compiles the dps category and the value to send to the bulb.

Credit has to go to Buckskin though as he supplied the important stuff. I just organised it into a way that worked for me!

1 Like

Yes!! The colors work now :tada:
I didn't even think about the warmth of white light

Thank you so much man!
And Mr. Buckskin as well :raised_hands:

Many thanks David for sharing this.

I updated your node to support status updates on deploy or if the bulb is changed from other place(original tuya app for example).
The color selector is now a button instead of dropdown for faster use.

It also auto changes to color mode in case any color is selected.

Its only missing dps24 (color update after deploy).

Hope you like it.

[{"id":"de52507a.9b7738","type":"tuya-smart-device","z":"3371c2c0.2266be","deviceName":"Lampa","disableAutoStart":false,"deviceId":"","deviceKey":"yourkey","deviceIp":"yourip","retryTimeout":"9999","findTimeout":"9999","tuyaVersion":"3.1","eventMode":"event-both","x":1030,"y":2380,"wires":[["79048cad60385052"],["945fdff2c22f9801"]]},{"id":"ea11f367.21a7","type":"ui_colour_picker","z":"3371c2c0.2266be","name":"","label":"","group":"ea78fcbd.2005b","format":"hsv","outformat":"object","showSwatch":false,"showPicker":true,"showValue":false,"showHue":false,"showAlpha":false,"showLightness":true,"square":"false","dynOutput":"false","order":12,"width":"1","height":"1","passthru":false,"topic":"24","topicType":"str","x":670,"y":2520,"wires":[["f2beb67f.41e808"]]},{"id":"13ad14f7.e42283","type":"function","z":"3371c2c0.2266be","name":"","func":"var topic = msg.topic\nvar payload = msg.payload\n\nmsg.payload = { \n    dps : topic,\n    set : payload\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":880,"y":2460,"wires":[["de52507a.9b7738"]]},{"id":"d9a55dfd.20ebd8","type":"ui_switch","z":"3371c2c0.2266be","name":"","label":"Lampa","tooltip":"","group":"ea78fcbd.2005b","order":9,"width":"0","height":"0","passthru":false,"decouple":"true","topic":"20","topicType":"str","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"x":650,"y":2360,"wires":[["13ad14f7.e42283"]]},{"id":"62a8bb81.35e964","type":"ui_slider","z":"3371c2c0.2266be","name":"","label":"Brightness","tooltip":"","group":"ea78fcbd.2005b","order":10,"width":"0","height":"0","passthru":false,"outs":"end","topic":"22","topicType":"str","min":"10","max":"1000","step":"10","x":670,"y":2440,"wires":[["13ad14f7.e42283"]]},{"id":"6c597fa0.fedfb8","type":"ui_slider","z":"3371c2c0.2266be","name":"","label":"Warm/Cool","tooltip":"","group":"ea78fcbd.2005b","order":11,"width":"0","height":"0","passthru":false,"outs":"end","topic":"23","topicType":"str","min":0,"max":"1000","step":"10","x":670,"y":2480,"wires":[["13ad14f7.e42283"]]},{"id":"f2beb67f.41e808","type":"function","z":"3371c2c0.2266be","name":"","func":"const colourValue = msg.payload\nmsg2 = {};\nmsg.payload = HSVtoTuyaColour(colourValue.h, colourValue.s * 100, colourValue.v * 100)\nmsg2.payload = true;\nreturn [msg,msg2];\n\nfunction HSVtoTuyaColour(hue, saturation, value) {\n\t\t// Note: hue = hue.hue MUST go last\n\t\tif (arguments.length === 1) {\n\t\t\tsaturation = hue.saturation;\n\t\t\tvalue = hue.value;\n\t\t\thue = hue.hue;\n\n\t\t}\n\n\t\t// Increase saturation & value to correct set [0, 1000]\n\t\tsaturation = saturation * 10;\n\t\tvalue = value * 10;\n\n\t\treturn HSVtoHexStr(hue, saturation, value);\n}\n\nfunction HSVtoHexStr(hue, saturation, value) {\n\t\tlet a = decimalToHex(hue, 4);\n\t\tlet b = decimalToHex(saturation, 4);\n\t\tlet c = decimalToHex(value, 4);\n\n\t\treturn a + b + c;\n\n\t}\n\t\nfunction decimalToHex(decimal, padCount = 2) {\n\t\treturn (decimal + Math.pow(16, padCount)).toString(16).slice(-padCount).toUpperCase();\n\n\t}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":820,"y":2520,"wires":[["13ad14f7.e42283"],["88fe8b2cbf95d270"]]},{"id":"945fdff2c22f9801","type":"function","z":"3371c2c0.2266be","name":"update","func":"if (msg.payload.state == \"CONNECTED\") {\n    return { payload: { operation: \"GET\", schema: true } };\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1030,"y":2440,"wires":[["de52507a.9b7738"]]},{"id":"79048cad60385052","type":"function","z":"3371c2c0.2266be","name":"","func":"var msg1 = null;\nvar msg2 = null;\nvar msg3 = null;\nvar msg4 = null;\n\n\nif (typeof msg.payload.data.dps[\"22\"] != \"undefined\") {\n    msg1 = {};\n    msg1.payload = msg.payload.data.dps[\"22\"];\n}\nif (typeof msg.payload.data.dps[\"20\"] != \"undefined\") {\n    flow.set(\"lampa\", msg.payload.data.dps[\"20\"]);\n    msg2 = {};\n    msg2.payload = msg.payload.data.dps[\"20\"];\n}\nif (msg.payload.data.dps[\"21\"] == \"colour\") {\n    msg3 = {};\n    msg3.payload = true;\n}\n\n\nif (msg.payload.data.dps[\"21\"] == \"white\") {\n    msg3 = {};\n    msg3.payload = false;\n}\n\nif (msg.payload.data.dps[\"23\"] != \"undefined\") {\n    msg4 = {};\n    msg4.payload = msg.payload.data.dps[\"23\"];\n}\n\n\nreturn [msg1, msg2, msg3, msg4];","outputs":4,"noerr":0,"initialize":"","finalize":"","x":440,"y":2440,"wires":[["62a8bb81.35e964"],["d9a55dfd.20ebd8"],["35363a363bb16058"],["6c597fa0.fedfb8"]]},{"id":"35363a363bb16058","type":"ui_switch","z":"3371c2c0.2266be","name":"","label":"Color","tooltip":"","group":"ea78fcbd.2005b","order":13,"width":"4","height":"1","passthru":false,"decouple":"true","topic":"21","topicType":"str","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"x":650,"y":2400,"wires":[["88fe8b2cbf95d270"]]},{"id":"88fe8b2cbf95d270","type":"function","z":"3371c2c0.2266be","name":"","func":"if (msg.payload == true) {\n    msg.payload = {\n        dps: 21,\n        set: \"colour\"\n    }\n\n} else {\n    msg.payload = {\n        dps: 21,\n        set: \"white\"\n    }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":780,"y":2400,"wires":[["de52507a.9b7738"]]},{"id":"ea78fcbd.2005b","type":"ui_group","name":"Dormitor","tab":"8e62dc69.dd4ad","order":4,"disp":true,"width":5,"collapse":false},{"id":"8e62dc69.dd4ad","type":"ui_tab","name":"Lumini","icon":"lightbulb_outline","order":6,"disabled":false,"hidden":false}]

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.