tinytuya icon indicating copy to clipboard operation
tinytuya copied to clipboard

Event listener

Open vvmichielvv opened this issue 4 years ago • 4 comments

Hi all,

I was Tuya dimming units that can also can be switched by a physical switch. I would like to receive an update through the socket connected to the device to know when that happens, so i can sync my Dashboard (Openhab). Anyone any success in getting this to work? I think i could add a listener to the socket on the device object directly, but it seems like a hack to me: what happens when the the socket closes and a new one is made? I assume the socket listener is gone then as well. Also, i may read data which is the response to a command instead of a physical switch...

Any thoughts?

thanks! Michiel

vvmichielvv avatar Jan 31 '21 10:01 vvmichielvv

Hi Michiel, tinytuya is a command line Tuya library that allows you to poll devices for state or change state (on/off/dim/color). A python script could be written to continually poll every X seconds and report changes (tinytuya also supports socket persistence). I'm not familiar enough with Openhab to know what you are needing. Does it work with MQTT?

jasonacox avatar Feb 01 '21 00:02 jasonacox

Hi all. I just started to work with tuya and I am also no expert in homeautomation or network communication. My target was to monitor if a switch is pressed. Of course I can do that by polling the status but I would prefer the switch to send an event which I can receive in python.

What I found out so far: I logged the communication on my router and analyzed it whit Wireshark. And it looks like the Smart Live App is not polling for status. But if I press the switch the app shows the new state right away. This also works if I disconnect the router from the internet.

Has anyone an idea how tuya is doing that? Is someone more experienced in network communication than me and could do a more detailed analysis of the communication?

If we find out how it works we could maybe develop the same functionality for python.

PS: thanks for tinytuya i really like it.

Florian-Mangold avatar Apr 25 '21 11:04 Florian-Mangold

Based on my research into other Tuya projects as well as the https://iot.tuya.com/ developer portal, I discovered two ways that Tuya devices communicate:

  • Tuya Cloud - as IoT devices, they are designed to connect with the TuyaCloud to post status changes as well as receive commands. This is how 3rd party integrations like Alexa are able to control and display Tuya devices. This is also why you are able to control your Tuya devices from your smart phone even when you are not on your local network.

  • Local Network - the Tuya devices also communicate on the local area network. It does so in two ways:

    1. UDP Broadcasts - Tuya devices broadcast basic information about themselves via UDP packets (port 6666 and 6667) - this payload contains useful but public information (ID, IP, Product SKU and Version) but does not provide state data or the ability to control it. This communication is use to help determine the IP address of Tuya devices that can change over time (DHCP) as well as discover new devices. This is used by tinytuya during the python -m tinytuya scan command.

    2. TCP Server - Tuya devices listen on port TCP 6668 for clients. Clients connect with this to get state data and to send control codes in an encrypted way. The encryption key is set when you pair the Tuya device with the Smartlife App (the first time or any subsequent time will change the key -- the key is stored in the Tuya device and in TuyaCloud). The Smartlife App uses this local TCP server and encryption key to connect to the Tuya devices directly. This is also how tinytuya works. It connects directly to the Tuya device over this TCP port and uses the encrypt key you provide (which can be pulled from TuyaCloud via the python -m tinytuya wizard command). Once a client like tinytuya is connected, the Tuya device will send state changes and heartbeat information over that TCP link for the duration of the TCP link. In your example, even when you disabled your connection to the internet, your SmartLife App was still connected to your Tuya device (TCP 6668) and therefore was able to reflect the state changes (turn on/off) of the device even without the internet connection to TuyaCloud.

My attempt to diagram this connectivity is here: https://raw.githubusercontent.com/jasonacox/tinytuya/master/docs/TinyTuya-diagram.svg

You can mimic the functionality that you are looking for in Python by keeping a persistent TCP connection to the Tuya devices. You can do that with tinytuya using the set_socketPersistent(False/True) function and create a heartbeat loop and event handler for state changes for each device. This is similar to what localtuya is doing for Home Assistant ingegration (see https://github.com/rospogrigio/localtuya).

Here is a possible (likely poor) example, but may help. This uses the raw send() and receive() functions in version tinytuya v1.2.5.

import tinytuya
import time

# tinytuya.set_debug(True)

d = tinytuya.OutletDevice('DEVICEID', 'DEVICEIP', 'DEVICEKEY')
d.set_version(3.3)
d.set_socketPersistent(True)

print(" > Send Request for Status < ")
payload = d.generate_payload(tinytuya.DP_QUERY)
d.send(payload)

while(True):
    # See if any data is available
    data = d.receive()
    print('Received Payload: %r' % data)

    # Send keyalive heartbeat
    print(" > Send Heartbeat Ping < ")
    payload = d.generate_payload(tinytuya.HEART_BEAT)
    d.send(payload)

Here is a sample output from a test I ran against a light switch. The light was on, I turned it off then back on. Notice the dps '1' state change:

 > Send Request for Status < 
Received Payload: {'devId': '047555462abc32a18791', 'dps': {'1': True, '9': 0}}
 > Send Heartbeat Ping < 
Received Payload: None
 > Send Heartbeat Ping < 
Received Payload: {'devId': '047555462abc32a18791', 'dps': {'1': False}, 't': 1619397248}
 > Send Heartbeat Ping < 
Received Payload: {'devId': '047555462abc32a18791', 'dps': {'9': 0}, 't': 1619397248}
 > Send Heartbeat Ping < 
Received Payload: None
 > Send Heartbeat Ping < 
Received Payload: {'devId': '047555462abc32a18791', 'dps': {'1': True}, 't': 1619397253}
 > Send Heartbeat Ping < 
Received Payload: {'devId': '047555462abc32a18791', 'dps': {'9': 0}, 't': 1619397253}
 > Send Heartbeat Ping < 
Received Payload: None

jasonacox avatar Apr 25 '21 20:04 jasonacox

Hi thank you so much. Your answer and example was super helpful.

Maybe you could consider to put your example also to the "Programming with TinyTuya" section on the main page.

I reused your code to control a Zigbee light whit a curtain control in a way that the light turns on when I close the curtain. My actual code is a little bit more complicated (depended on time and curtain position). The control works now in real time.

To sum it up I'm super happy.

My simplified code (maybe helpful for someone else):

b = Bridge(ip='???',username="???")
b.connect()
LN=[8,9]

def Light(x):
    if x == 'on':
        b.set_light(LN, 'on', True)
    elif x == 'off':
        b.set_light(LN, 'on', False)

  
d = tinytuya.OutletDevice('???', '???', '???')
d.set_version(3.3)
d.set_socketPersistent(True)

print(" > Send Request for Status < ")
payload = d.generate_payload(tinytuya.DP_QUERY)
d.send(payload)

try:
    while(True):
        # See if any data is available
        data = d.receive()
#        print('Received Payload: %r' % data)
        print(data)        
        
        if data != None:
            if data.get('dps') ==  {'1': 'stop'}:
                Light('')
            if data.get('dps') ==  {'1': 'close'}:
                Light('on')
            if data.get('dps') ==  {'1': 'open'}:
                Light('off')

        # Send keyalive heartbeat
#        print(" > Send Heartbeat Ping < ")
        payload = d.generate_payload(tinytuya.HEART_BEAT)
        d.send(payload)
        
except KeyboardInterrupt:
    pass

Florian-Mangold avatar May 23 '21 09:05 Florian-Mangold