jasonacox / tinytuya

Python API for Tuya WiFi smart devices using a direct local area network (LAN) connection or the cloud (TuyaCloud API).
MIT License
937 stars 167 forks source link

Rework Zigbee Gateway handling to support multiple devices with persistent connections #226

Closed uzlonewolf closed 1 year ago

uzlonewolf commented 1 year ago

This PR updates Zigbee Gateway handling to be a parent/child model. First a connection is made to the gateway, and then one or more children are attached to that gateway. This allows persistence to be used to receive async updates for multiple different devices simultaneously. When sending commands it also makes an attempt to only return responses from the selected child, even if an async update from a different child is received first.

The wizard has been updated to include the node_id and parent device id in the devices.json file.

As the device id of a child is used for identification, all children must have unique device ids. Tuya's cloud assigns a unique id to every device so this should not be a problem as long as the correct device id from devices.json is used.

When a response containing a known cid is received, result['device'] contains a reference to the child device object and can be used in place of the original object.

import tinytuya
import time

# configure the parent device
gw = tinytuya.Device( 'eb...4', address=None, local_key='aabbccddeeffgghh', persist=True, version=3.3 )

print('GW IP found:', gw.address, '- getting status')

# configure one or more children.  Every dev_id must be unique!
zigbee1 = tinytuya.OutletDevice( 'eb14...w', cid='0011223344556601', parent=gw )
zigbee2 = tinytuya.OutletDevice( 'eb04...l', cid='0011223344556689', parent=gw )

print(zigbee1.status())
print(zigbee2.status())

print(" > Begin Monitor Loop <")
pingtime = time.time() + 9

while(True):
    if( pingtime <= time.time() ):
        payload = gw.generate_payload(tinytuya.HEART_BEAT)
        gw.send(payload)
        pingtime = time.time() + 9

    # receive from the gateway object to get updates for all sub-devices
    print('recv:')
    data = gw.receive()
    print( data )

    # data['device'] contains a reference to the device object
    if data and 'device' in data and data['device'] == zigbee1:
        print('toggling device state')
        time.sleep(1)
        if data['dps']['1']:
            data['device'].turn_off(nowait=True)
        else:
            data['device'].turn_on(nowait=True)

Closes #31

uzlonewolf commented 1 year ago

Turns out when setting Zigbee DPs, "devId" must match the cid or be omitted. New "zigbee" device type added to handle this.

jasonacox commented 1 year ago

@uzlonewolf this is great! You're amazing. Thank you! I don't have a zigbee gw to test but I trust you. I will try to pick one up for a 2nd test.

I think we should add your zigbee.py example above in the examples folder and/or a note of zigbee parent/child support in the README (with reference to the example).

uzlonewolf commented 1 year ago

This was the one I was testing with: https://www.amazon.com/dp/B0969ZHH83

I also picked up a https://www.amazon.com/dp/B09LM644XJ to see how it acts when not registered but haven't had a chance to mess with it yet.

I'll try to get that example added later today.