jasonacox / tuyapower

Python module to read status and energy monitoring data from Tuya based WiFi smart devices. This includes state (on/off), current (mA), voltage (V), and power (wattage).
MIT License
142 stars 22 forks source link

Data only updates after opening app (Denver SHP-100) #7

Closed StephanMeijer closed 4 years ago

StephanMeijer commented 4 years ago

Using Denver SHP-100, testing with waterboiler.

>>> tuyapower.devicePrint(PLUGID,PLUGIP,PLUGKEY,PLUGVERS)
TuyaPower (Tuya Power Stats) [0.0.20]

Device ... at 192.168.90.139 key ... protocol 3.3:
    Switch On: True
    Power (W): 2027.600000
    Current (mA): 8662.000000
    Voltage (V): 233.400000
    Projected usage (kWh):  Day: 48.662400  Week: 340.636800  Month: 1476.092800

>>> tuyapower.devicePrint(PLUGID,PLUGIP,PLUGKEY,PLUGVERS)
TuyaPower (Tuya Power Stats) [0.0.20]

Device ... at 192.168.90.139 key ... protocol 3.3:
    Switch On: True
    Power (W): 2027.600000
    Current (mA): 8662.000000
    Voltage (V): 233.400000
    Projected usage (kWh):  Day: 48.662400  Week: 340.636800  Month: 1476.092800

>>> tuyapower.devicePrint(PLUGID,PLUGIP,PLUGKEY,PLUGVERS)
TuyaPower (Tuya Power Stats) [0.0.20]

Device ... at 192.168.90.139 key ... protocol 3.3:
    Switch On: True
    Power (W): 2030.300000
    Current (mA): 8668.000000
    Voltage (V): 233.400000
    Projected usage (kWh):  Day: 48.727200  Week: 341.090400  Month: 1478.058400

After closing app and turning off boiler:

>>> tuyapower.devicePrint(PLUGID,PLUGIP,PLUGKEY,PLUGVERS)
TuyaPower (Tuya Power Stats) [0.0.20]

Device ... at 192.168.90.139 key ... protocol 3.3:
    Switch On: True
    Power (W): 2030.300000
    Current (mA): 8668.000000
    Voltage (V): 233.400000
    Projected usage (kWh):  Day: 48.727200  Week: 341.090400  Month: 1478.058400

After opening app:

>>> tuyapower.devicePrint(PLUGID,PLUGIP,PLUGKEY,PLUGVERS)
TuyaPower (Tuya Power Stats) [0.0.20]

Device ... at 192.168.90.139 key ... protocol 3.3:
    Switch On: True
    Power (W): 0.000000
    Current (mA): 0.000000
    Voltage (V): 236.700000
    Projected usage (kWh):  Day: 0.000000  Week: 0.000000  Month: 0.000000

It seems that the app is doing some sort of manual update request. Could it be some caching issue? The problem not only occurs if the Plug is connected to the internet, but also if I am blocking any traffic from the plug to outside servers, so it occurs independently from the connection to the Tuya cloud.

jasonacox commented 4 years ago

Hi @StephanMeijer,

That's very interesting. It has been my experience that Tuya devices will only allow one connection at a time, which means that when the app is running tuyapower will return with an error. The devicePrint() function doesn't cache any of the data (it actually sets all the values to -99 before polling), so if it is displaying any data, that is coming back from the smartplug itself. In the example you give, it seems like the smartplug is providing data even when the app is running but it does look like it is not updating over time (is that true?).

If you stop the app and keep it off for >5 minutes, are you able to get tuyapower to give you correct and changing data? In my experience, for the 3.1 and 3.3 plugs I have, even after sopping the app I have to wait a while before the smartplug start responding with valid data. Alternatively, I suppose there could be some key/authentication issue.

Try running something like this for several minutes (with the app closed) to see what happens:

import time
import tuyapower

PLUGID = '01234567891234567890'
PLUGIP = '10.0.1.99'
PLUGKEY = '0123456789abcdef'
PLUGVERS = '3.3'

while True:
    tuyapower.devicePrint(PLUGID,PLUGIP,PLUGKEY,PLUGVERS)
    time.sleep(5)
StephanMeijer commented 4 years ago

Sorry @jasonacox. I was so annoyed, I sent the product back. Now switched to the TP-Link HS-110.

jasonacox commented 4 years ago

Thanks for the update @StephanMeijer - Let me know how the TP-Link HS_110 works for you.

Bjarkes commented 3 years ago

Hi @jasonacox, I am experiencing the exact same thing with the Denver SHP-100's I have.

  1. I get the same result constantly from tuyapower
  2. I open the Smart Life app - Updating normally in the app
  3. tuyapower stops responding entirely
  4. I close down the Smart Life app
  5. tuyapower is now getting another result than in 1., but keep responding the same result

It seems like it just always send back the latest of a cached value, and only update the cache when opening the Smart Life / Tuya app.

Any ideas?

Bjarkes commented 3 years ago

Update: It seems like if I let it run for 5-10 minutes (without opening any Tuya app) then sometimes it will update the value once and then keep that value for the next 5-10 minutes. Other times I can let it run for an hour with no update.

jasonacox commented 3 years ago

Hi @Bjarkes - this happens with some plugs.
1) When SmartLife app is running, the Tuya device will reject all other attempts to connect to it. That is why tuyapower stops working when you run the app. 2) Some plugs require an UPDATEDPS command to update their power data points immediately, otherwise they cache as you noted.

Here is an example:

import tinytuya
import time

# Connect to the device - replace with real values
d=tinytuya.OutletDevice(DEVICEID, DEVICEIP, DEVICEKEY)
d.set_version(3.3)

# Option for Power Monitoring Smart Plugs - Some require UPDATEDPS to update power data points
payload = d.generate_payload(tinytuya.UPDATEDPS,['18','19','20'])
d.send(payload)
sleep(1)

# Get the status of the device 
# e.g. {'devId': '0071299988f9376255b', 'dps': {'1': True, '3': 208, '101': False}}
data = d.get_status()
print(data)

You can also try this script to monitor the plug and see if you are getting more frequent power updates:

# TinyTuya Example
# -*- coding: utf-8 -*-
"""
 TinyTuya - Example showing async persistent connection to device with
 continual loop watching for device updates.

 Author: Jason A. Cox
 For more information see https://github.com/jasonacox/tinytuya

"""
import tinytuya

# 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)

print(" > Begin Monitor Loop <")
while(True):
    # See if any data is available
    data = d.receive()
    print('Received Payload: %r' % data)

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

    # Option - Some plugs require an UPDATEDPS command to update their power data points

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

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

    print(" > Send DPS Update Request < ")
    payload = d.generate_payload(tinytuya.UPDATEDPS,['18','19','20'])
    d.send(payload)

    # See if any data is available
    data = d.receive()
    print('Received Payload: %r' % data)
Bjarkes commented 3 years ago

Hi @jasonacox, thank you for you reply

I tested both scripts, with no success. One thing I noticed is that if I run the "script to monitor" and then open the Smart Life app, then I get all the updates, but as soon as I close down the app it goes back to receiving the same response again and again. I wonder what kind of signal the app is sending to trigger the updates.

Here is an output sample, you can see that I got different payload and in the bottom I start to get the same after I closed down the app.

 > Send Request for Status <
Received Payload: None
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 109, '19': 206, '20': 2290, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 111, '19': 196}, 't': 1627030593}
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 111, '19': 196, '20': 2290, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 102, '19': 202}, 't': 1627030598}
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: None
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 112, '19': 210, '20': 2294, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 106, '19': 193}, 't': 1627030609}
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 106, '19': 193, '20': 2294, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 111, '19': 197, '20': 2290}, 't': 1627030613}
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 101, '19': 193}, 't': 1627030618}
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 111, '19': 192}, 't': 1627030623}
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: None
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 110, '19': 198, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 121, '19': 218}, 't': 1627030633}
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 121, '19': 218, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 119, '19': 204, '20': 2290}, 't': 1627030638}
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 104, '19': 235}, 't': 1627030643}
 > Send DPS Update Request <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'18': 129, '19': 192, '20': 2286}, 't': 1627030648}
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: None
 > Send DPS Update Request <
Received Payload: None
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 103, '19': 217, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: None
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 103, '19': 217, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: None
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 103, '19': 217, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: None
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 103, '19': 217, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: None
Received Payload: None
 > Send Heartbeat Ping <
 > Send Request for Status <
Received Payload: {'devId': '71316800c82b966a7efb', 'dps': {'1': True, '9': 0, '18': 103, '19': 217, '20': 2286, '21': 1, '22': 634, '23': 34366, '24': 19950, '25': 1044}}
 > Send DPS Update Request <
Received Payload: None
Received Payload: None
Bjarkes commented 3 years ago

After hours of investigation and even trying our other libraries (including Tuya's own) with no luck, always the same behavior, I found the solution.

It all comes down to how the generate_payload method with the UPDATEDPS command is called. If I remove the data and just write generate_payload(tinytuya.UPDATEDPS) then it works perfectly, but with the data like in your example: generate_payload(tinytuya.UPDATEDPS,['18','19','20']) it just return None, unless I have the "Electric" tab open for the device in the Smart Life app.

It must be some kind of bad implementation in their end I suppose. Their own tools like api, sdk and add-ons (like fx. the tuya 2 for Home Asssistant) does not work either unless the app is used as described or now with my tinytuya loop.

jasonacox commented 3 years ago

Nice find!! I want to add a note to my example script for that use case. Can you send me the script you used that worked? Was it basically this?

    print(" > Send DPS Update Request < ")
    payload = d.generate_payload(tinytuya.UPDATEDPS)
    d.send(payload)

If so, that is equivalent to:

    print(" > Send DPS Update Request < ")
    payload = d.generate_payload(tinytuya.UPDATEDPS,[1])
    d.send(payload)

It could also be an issue with that Tuya device not accepting quoted DPS index values in the JSON payload. This may also work:

    print(" > Send DPS Update Request < ")
    payload = d.generate_payload(tinytuya.UPDATEDPS,[18,19,20])
    d.send(payload)

It would be interesting to see what works for you. :)

Thanks for the update!

Bjarkes commented 3 years ago

Yes, your first example is basically what works, I just run that in a loop and then I get updated status values every 5 second or so.

The last example also works, that is d.generate_payload(tinytuya.UPDATEDPS,[18,19,20]), so it seems like the device does not accept quoted DPS index values as you suggested.

But the d.generate_payload(tinytuya.UPDATEDPS,[1]) is not equivalent to no data input, using that it gives no updates as before. Looking at the tinytuya source code, it actually looks like the default is [18,19,20] without quotes. The payload_dict["default"] has:

UPDATEDPS: {
            "hexByte": "12",
            "command": {"dpId": [18, 19, 20]}
        },

Feel free to come with more suggestions on what to test, the more we learn and can share, the less other people have to waste time scratching their head like I did.

LemonzDEV commented 2 years ago

I was going crazy... VERY THANKS to the last 2 comments!