Local tuya light bulb protocol version 3.3

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

1 Like

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

Oh yeah, I’d recently changed that to see what happens. Anyway I still get some error, maybe a different one. Here’s what I get when I set the IP.

Boolean status of default property: false.
on
Connected to device!
Error! Error: Error from socket
    at Socket.client.on.err (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:323:30)
    at Socket.emit (events.js:197:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at processTicksAndRejections (internal/process/next_tick.js:76:17)
Disconnected from device.
Connected to device!
Error! Error: Error from socket
    at Socket.client.on.err (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:323:30)
    at Socket.emit (events.js:197:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at processTicksAndRejections (internal/process/next_tick.js:76:17)
Disconnected from device.
Connected to device!
Error! Error: Error from socket
    at Socket.client.on.err (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:323:30)
    at Socket.emit (events.js:197:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at processTicksAndRejections (internal/process/next_tick.js:76:17)
Disconnected from device.

That message points to the wrong protocol being used. Did you try setting it to 3.3?

Having changed to version 3.3, I get this:

Connected to device!
(node:82218) UnhandledPromiseRejectionWarning: Error: connection timed out
    at Socket.client.setTimeout (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:292:18)
    at Object.onceWrapper (events.js:285:13)
    at Socket.emit (events.js:197:13)
    at Socket._onTimeout (net.js:447:8)
    at listOnTimeout (timers.js:327:15)
    at processTimers (timers.js:271:5)
(node:82218) 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:82218) [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.
Disconnected from device.
Data from device: data format error
/Users/Mudi/Programming/MyCode/IOT/Home Assistant/syska.js:35
        console.log(`Boolean status of default property: ${data.dps['1']}.`);
                                                                   ^

TypeError: Cannot read property '1' of undefined
    at TuyaDevice.device.on.data (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/syska.js:35:68)
    at TuyaDevice.emit (events.js:197:13)
    at TuyaDevice._packetHandler (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:414:10)
    at packets.forEach.packet (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:315:43)
    at Array.forEach (<anonymous>)
    at Socket.client.on.data (/Users/Mudi/Programming/MyCode/IOT/Home Assistant/node_modules/tuyapi/index.js:311:19)
    at Socket.emit (events.js:197:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at Socket.Readable.push (_stream_readable.js:224:10)

Since I was able to pull the status of the device at least when I put version 3.2, I figured I don’t have 3.3.

With 3.3, even node filename.js status does not work.

While looking at the pytuya docs I found this:

data = d.status() # NOTE this does NOT require a valid key

You may have a 3.2 version and the wrong local key. How did you get that value? If you add / remove the bulb from the app the key is regenerated.