jasonacox / tinytuya

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

Question about monitoring multiple devices #414

Closed 3735943886 closed 8 months ago

3735943886 commented 8 months ago

Hi. Thank you for the great project.

I have been attempting to create automation scripts using Python for my Tuya WiFi devices, and I have found your examples, async_send_receive.py and monitor.py, to be extremely useful. I have successfully managed to create some scripts based on these examples.

However, I am curious about the best practices for monitoring multiple devices simultaneously. At present, I am creating multiple threads and looping through each device in each thread. I am wondering if there is a more efficient way to receive DP_QUERY asynchronously from multiple devices using a single loop (similar to the WaitForMultipleObjects function from the Windows API, as opposed to WaitForSingleObject).

I look forward to your guidance on this matter. Thank you.

jasonacox commented 8 months ago

Hi @3735943886 , thanks for the kind note!

As to the way to connect to multiple devices similar to the monitor.py script and wait for updates from them, that could be done with select() or using the asyncio library. It is on my long term TODO to see if there is an easy way to introduce that into the tinytuya library.

uzlonewolf commented 8 months ago

It's neither easy nor pretty, but it is possible. Both the scanner and the multi-server I'm working on do it. The biggest issue is TinyTuya is written around blocking sockets which causes everything else to come to a grinding halt when a connection is made or a read is requested. You basically need to create a wrapper around the device which implements a non-blocking connect and a state machine for handshaking and keeping track of read requests.

sjpbailey commented 8 months ago

Jason,  Do not know if this is related however you can use groups in the Tuya Smart phone app for simultaneous control of multiple devices.Is this readable and controllable in the API? It creates a separate node in the phone app. Maybe it adds it to the API?Say i have a stage and i want all my light on simultaneously.  Anyway pardon the interruption just a thought.  i would like control of multiple devices on the cloud like the phone app.RegardsOn Oct 30, 2023, at 12:06 PM, uzlonewolf @.***> wrote: It's neither easy nor pretty, but it is possible. Both the scanner and the multi-server I'm working on do it. The biggest issue is TinyTuya is written around blocking sockets which causes everything else to come to a grinding halt when a connection is made or a read is requested. You basically need to create a wrapper around the device which implements a non-blocking connect and a state machine for handshaking and keeping track of read requests.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you are subscribed to this thread.Message ID: @.***>

3735943886 commented 8 months ago

I have attempted to use asyncio, but failed due to the Device.receive() function blocking the main event loop, as mentioned above. However, after testing my multithread monitoring code for approximately 24 hours, it has been found to function well.

Here for review of any potential logical errors or other issues are welcome to be pointed out. Additionally, anyone who requires continuous monitoring of multiple devices is welcome to utilize this code. Thank you.

#!/usr/bin/python3

import threading
import tinytuya

TUYALISTENER = [ {
                'class' : tinytuya.OutletDevice,
                'args' : {
                        'dev_id' : 'DEVICEID1',
                        'address' : 'ADDRESS1',
                        'local_key' : 'KEY1',
                        'version' : 3.4,
                        'persist' : True
                }
        }, {
                'class' : tinytuya.OutletDevice,
                'args' : {
                        'dev_id' : 'DEVICEID2',
                        'address' : 'ADDRESS2',
                        'local_key' : 'KEY2',
                        'version' : 3.4,
                        'persist' : True
                }
        }, {
                'class' : tinytuya.OutletDevice,
                'args' : {
                        'dev_id' : 'DEVICEID3',
                        'address' : 'ADDRESS3',
                        'local_key' : 'KEY3',
                        'version' : 3.4,
                        'persist' : True
                }
        }
]

def tuyareceiver(tuyainfo, tuyadpscallback):
        tuyadev = tuyainfo['class'](**tuyainfo['args'])
        tuyadev.send(tuyadev.generate_payload(tinytuya.DP_QUERY))
        while True:
                dps = tuyadev.receive()
                if dps == None:
                        tuyadev.send(tuyadev.generate_payload(tinytuya.HEART_BEAT))
                else:
                        tuyadpscallback(tuyadev, dps)

def dpsconsumer(tuyadev, data):
        if (tuyadev.id == 'DEVICEID1'):
                print(data)

        elif (tuyadev.id == 'DEVICEID2'):
                print(data)

        elif (tuyadev.id == 'DEVICEID3'):
                print(data)

        else:
                pass

th = None
for tuyainfo in TUYALISTENER:
        th = threading.Thread(target = tuyareceiver, args = (tuyainfo, dpsconsumer))
        th.start()

th.join()
uzlonewolf commented 8 months ago

@sjpbailey

Is this readable and controllable in the API?

Yes.

  1. Get a list of "Spaces" (SmartLife accounts linked to your Cloud account)
    import tinytuya
    import json
    c = tinytuya.Cloud()
    json.dumps( c.cloudrequest( '/v2.0/cloud/space/child' ), indent=2 )

1b. (Optional) Get the name associated with each space

json.dumps( c.cloudrequest( '/v2.0/cloud/space/123456' ), indent=2 )
  1. Get a list of groups in a space

    json.dumps( c.cloudrequest( '/v2.0/cloud/thing/group', query={'space_id':123456, 'page_size':10, 'page_no':1} ), indent=2 )
  2. Get a list of properties in a group

    json.dumps( c.cloudrequest( '/v2.0/cloud/thing/group/7890123/properties' ), indent=2 )
  3. Send a command to the group

    json.dumps( c.cloudrequest( '/v2.0/cloud/thing/group/properties', post={'group_id':7890123, 'properties':'{"switch_led": true}'} ), indent=2 )

1-3 only need to be done once to get the group id and property list. After that just repeat 4. as needed.

jasonacox commented 8 months ago

after testing my multithread monitoring code for approximately 24 hours, it has been found to function well.

Multithreading should be fine. This is I/O bound versus CPU bound so the threading overhead should be low. Technically python just runs thread serially on a single core anyway.

biggest issue is TinyTuya is written around blocking sockets

@uzlonewolf Which gives me a terrifying idea... 😱 I guess it is close to Halloween afterall. 🎃

What if we created a new XenonDevice class (or even a more base AsyncDeivce base) to use asyncio and still preserved the existing Device class with the same blocking-like API as an abstraction class above the async handling while also exposing the async hooks to others who wanted to use it? Yes, crazy and it gives me a headache too. But I feel like this is going to keep coming up and multithreading isn't a long term answer for everyone.

sjpbailey commented 8 months ago

Thank You Jason!  I know If you want to know anything about Tuya in any respect you're the Expert!Thanks again!On Oct 30, 2023, at 7:34 PM, uzlonewolf @.***> wrote: @sjpbailey

Is this readable and controllable in the API?

Yes.

Get a list of "Spaces" (SmartLife accounts linked to your Cloud account)

import tinytuya import json c = tinytuya.Cloud() json.dumps( c.cloudrequest( '/v2.0/cloud/space/child' ), indent=2 ) 1b. (Optional) Get the name associated with each space json.dumps( c.cloudrequest( '/v2.0/cloud/space/123456' ), indent=2 )

Get a list of groups in a space

json.dumps( c.cloudrequest( '/v2.0/cloud/thing/group', query={'space_id':123456, 'page_size':10, 'page_no':1} ), indent=2 )

Get a list of properties in a group

json.dumps( c.cloudrequest( '/v2.0/cloud/thing/group/7890123/properties' ), indent=2 )

Send a command to the group

json.dumps( c.cloudrequest( '/v2.0/cloud/thing/group/properties', post={'group_id':7890123, 'properties':'{"switch_led": true}'} ), indent=2 ) 1-3 only need to be done once to get the group id and property list. After that just repeat 4. as needed.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

3735943886 commented 8 months ago

What if we created a new XenonDevice class (or even a more base AsyncDeivce base) to use asyncio and still preserved the existing Device class with the same blocking-like API as an abstraction class above the async handling while also exposing the async hooks to others who wanted to use it? Yes, crazy and it gives me a headache too. But I feel like this is going to keep coming up and multithreading isn't a long term answer for everyone.

I am looking forward to it. Thank you.

sjpbailey commented 8 months ago

Jason,

Pardon the intrusion i received this notice from Tuya. I haven't had to pay for the Tuya site in three years do i have to pay something for the service now? Noticed my "Groups" were also depreciated?

Highest Regards, Bailey

Tuya IoT Platform Service Notification

Dear developer,

Your subscription IoT Core, Cloud Develop Base Resource Trial remaining usage is 0.00%. Once the quota limit is exceeded, the service will be suspended.

If you want to raise the limit,

you can click the link below to upgrade your plan.

https://www.tuya.com/vas/commodity/IOT_CORE_V2

You are the account owner of the Tuya IoT Platform and have the right to be notified of any change in your subscription plan. If you do not use the service mentioned above in your business, please ignore this email.

If you are unclear about how to proceed, please contact your account manager or file a service ticket.

This is an automated email, please do not reply to this email. If you have any questions, please email us at @.***