Local tuya light bulb protocol version 3.3

I am a proud user of home assistant and all my modules work offline. I got a new cheap Tuya controlled smart light bulb which unfortunately can’t be flashed with tasmota (I did this with others before but they patched it) using https://github.com/ct-Open-Source/tuya-convert.

Since I refuse to call the cloud to turn on a light, I decided to create a custom component based on https://github.com/clach04/python-tuya. Unfortunately my light is using a brand new protocol version 3.3 that broke python-tuya, so I found this: https://github.com/codetheweb/tuyapi

Kudos to the team that managed to reverse engineer 3.3. Since I expect this to be temporary, I did a quick and dirty nodejs script that I can run in HA to control the light. You just need to extract the local key / id of your device using some sort of SSL proxy such as charles and your phone.

Components:

Controller: https://gist.github.com/rdelcorro/f8e7a6a919a53b0c00f77c7256b5e445
Dockerfile to embed it into the raspi image: https://gist.github.com/rdelcorro/f1ddae46d25842b83bc8d5b0f97ecabd (not needed if you don’t use docker)

Config snippet:

switch porch local:
  platform: command_line
  switches:
    porch_local:
      command_on: "/usr/bin/node /tuyaporch/tuyaporch.js on"
      command_off: "/usr/bin/node /tuyaporch/tuyaporch.js off"
      command_state: "/usr/bin/node /tuyaporch/tuyaporch.js status"
      friendly_name: Porch light tuya local
2 Likes

Hi, I’m new to HA.

Was also trying to control my Tuya bulb locally. I read a lot of threads mentioning tuyaapi, mytuya.py, switch.py etc but didn’t really get anywhere…

Anyway I’ve got my localKey (finally) and id of device. I’ll try using the code you’ve provided although not sure how the javascript files will fit in my HA project, but i’ll figure it out.

Do you know if one can use TuyaApi to change colors of the Tuya bulb, or only On/Off functionalities are supported?

Also why do you expect this to be temporary?

Thanks for the code.

Yes, you can change the color or any parameter with tuyaapi.

Its temporary since I am waiting until the new protocol version (3.3) is supported in tuya-convert or at the bare minimum, pytuya.

Ideally you should:

  • If the bulb you have uses version 3.2 or below try using tuya-convert and get rid of the software via OTA (over the air update) and install tasmota which will let you control the light trivially using mqtt

  • If you prefer to keep the stock firmware you can use pytuya directly which is very easy to integrate with HA using a custom component. Attaching a sample component (I did not author it): https://github.com/rdelcorro/pytuyacustomcomponentha

  • If you have the same version as me (3.3) the only current supported library is TuyaApi which unfortunately is written in js so my current post applies. What I suggest to do is:

    • Install npm / nodejs in your machine
    • edit the sample code I pasted in this thread with your api key / id and test it like so:
      • node scriptname.js status

If it works, then you can just integrate it as I explained before. If not, you may be in 3.2. You can edit the same sample and change the version.

The upstream python-tuya has merged in support for protocol 3.3, see this

This fork has updates for Python 3.5 and should be a good base for a custom component for HA to do tuya local control.

Got a couple of medion plugs yesterday from aldi Australia. Tuya convert no luck. Local protocol neither. The nodered tuya node seems broken until the dev upgrades the node tuyapi module.

Thanks for this I’ll test it tonite. Do you know if we can pull power consumption ?

Also how do you know when the fw has been patched against tuya-convert ?

Awesome! I guess I was impatient. I will be creating the custom component based on the py version soon

If its available in the app it should be, but it depends on the library. I do not have a device that has that capability to test.

Version 3.3 is apparently new and the Tuya folks are now forcing the upgrade. Tuya convert just doesn’t work against it yet

Finally I got the fully python version working: https://github.com/rdelcorro/pytuyacustomcomponentha

Instructions:

Copy the folder localtuya into your home assistant custom_components folder (create the folder in the same place as your configuration.yaml lives)

Set a config like so:

switch:
  - platform: localtuya
    host: 192.168.0.1
    local_key: 1234567891234567
    device_id: 12345678912345671234
    name: tuya_01

For now it only supports on / off but it can be extended to other functions such as color or brightness. I will work on those later. In the mean time I am happy to remove nodejs from my installation

Apparently there is a bug on the pytuya implementation that causes this:

2019-07-05 16:02:02 ERROR (MainThread) [homeassistant.helpers.entity] Update for switch.porch_local fails
Traceback (most recent call last):
File “/usr/src/app/homeassistant/helpers/entity.py”, line 220, in async_update_ha_state
await self.async_device_update()
File “/usr/src/app/homeassistant/helpers/entity.py”, line 377, in async_device_update
await self.hass.async_add_executor_job(self.update)
File “/usr/local/lib/python3.7/concurrent/futures/thread.py”, line 57, in run
result = self.fn(*self.args, **self.kwargs)
File “/config/custom_components/localtuya/switch.py”, line 138, in update
status = self._device.status(self._switch_id)
File “/config/custom_components/localtuya/switch.py”, line 96, in status
self._cached_status = self.__get_status(switchid)
File “/config/custom_components/localtuya/switch.py”, line 73, in __get_status
status = self._device.status()[‘dps’][switchid]
File “/config/custom_components/localtuya/pytuya/init.py”, line 307, in status
result = cipher.decrypt(result, False)
File “/config/custom_components/localtuya/pytuya/init.py”, line 87, in decrypt
raw = cipher.decrypt(enc)
File “/usr/local/lib/python3.7/site-packages/Crypto/Cipher/_mode_ecb.py”, line 195, in decrypt
raise ValueError(“Data must be aligned to block boundary in ECB mode”)
ValueError: Data must be aligned to block boundary in ECB mode

I can not repro it on my macbook but I can on linux.

Hi Boblatino,

Tried all that you asked me to do. Good news, your javascript code worked for me, but with version 3.2. Luckily you mentioned trying to change that, otherwise I would have given up. Now at least I know my local key and id are valid.

By the way, wasn’t sure how to install node / npm on my raspberry pi. I have hassio installed, so figured there is python installed for home assistant but no node. Is there a way to install node with hassio or just install home assistant on my laptop with virtualenv (as well as node separately)?

So since I have Tuya 3.2, I’ll try again adding the custom component you created which in turn will use py-tuya.

Noob question. For the pytuya configurations you mentioned earlier, in:

switch:
  - platform: localtuya
    host: 192.168.0.1
    local_key: 1234567891234567
    device_id: 12345678912345671234
    name: tuya_01

do I have to put the host IP address of my home assistant device or the tuya bulb?

Thanks for the detailed guide.

I am not an expert on Hassio but there is a thread talking about it here: NodeJS on Hass.io. If your pi is used just for home assistant you could install it locally:

wget http://node-arm.herokuapp.com/node_latest_armhf.deb
sudo dpkg -i node_latest_armhf.deb

You should put the ip address of the bulb. You can get it from your router dhcp table.
The python integration works fine but the status is flaky due to the decryption failure I mentioned before so the switch will “flip” a few times in the gui. At the moment I am still waiting on a fix from the pytuya owner

I had blocked internet access to the bulb and after a week, it just stopped responding to local commands!! Apparently there is a failsafe that disables local access if its not connected to the internet which is insane. As soon as I re enabled internet, the bulb started to respond again. I just can’t wait to get rid of this firmware

I’m also annoyed with Tuya actually. Since my last post, I haven’t really advanced. Python-tuya never worked so I decided to retry with tuya-api + javascript only.

Although your javascript code worked initially, I had only tried getting status. Apparently that’s all that works. I am not able to actually switch on/off… The command goes through but the bulb never changes its state :confused:

Here’s the output of my code:

You can see the status does not change to True… Nor does the bulb switch on. Or off when it is already on.

The ones that you have successfully flashed to tasmota have never caused any problems?

I am sorry to hear that. Did you keep the code as is? One possibility is that you are not waiting before exiting so the command is never sent but can’t be sure without seeing the code

No, I use them every day and zero problems since day 0. Tasmota is awesome

This is the actual code I’m using… haven’t really modified except for the device data

const TuyAPI = require('tuyapi');

var args = process.argv.slice(2);

app = function() {
    const device = new TuyAPI({
    // ip: '192.168.1.100',
    id: '84045520807d3a2b43c1',
    key: '31bbbe70ced2a84c',
    // version: 3.2});
    });

    // Find device on network
    device.find().then(() => {
        // Connect to device
        device.connect();
    });
    
    // Add event listeners
    device.on('connected', () => {
        console.log('Connected to device!');
    });
    
    device.on('disconnected', () => {
        console.log('Disconnected from device.');
    });
    
    device.on('error', error => {
        console.log('Error!', error);
    });
    
    device.on('data', data => {
        console.log('Data from device:', data);
    
        console.log(`Boolean status of default property: ${data.dps['1']}.`);
        console.log(args[0])
        if (args[0] == 'status') {
            if (data.dps['1'] == true ) {
                process.exit()
            } else {
                process.exit(1)
            }
        }
    
        if (args[0] == 'on') {
            device.set({set: true});
        }

        if (args[0] == 'off') {
            device.set({set: false});
        }

        process.exit(0)
    });
}

for (i=0; i< 15; i++) {
    setTimeout(app, i * 2000);
}

That looks good to me. Does you bulb work correctly with the app?

I did try to remove the process.exit(0) thinking that might be the problem. However it kept getting disconnected. I’m pasting the output when I comment out exit() line.

Boolean status of default property: false.
on
(node:79379) UnhandledPromiseRejectionWarning: Error: find() timed out. Is the device powered on and the ID or IP correct?
    at pTimeout (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:590:13)
    at Timeout.setTimeout [as _onTimeout] (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/p-timeout/index.js:20:13)
    at listOnTimeout (timers.js:327:15)
    at processTimers (timers.js:271:5)
(node:79379) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:79379) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:79379) UnhandledPromiseRejectionWarning: Error: find() timed out. Is the device powered on and the ID or IP correct?
    at pTimeout (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:590:13)
    at Timeout.setTimeout [as _onTimeout] (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/p-timeout/index.js:20:13)
    at listOnTimeout (timers.js:327:15)
    at processTimers (timers.js:271:5)
(node:79379) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

Yes my bulb works correctly with the app.

I am observing that you are not setting the ip address of the device and rather using the find feature which is indeed timing out. Can you set the ip instead?

The other difference is that you are using the old protocol version 3.2 instead of the new 3.3