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
136 stars 20 forks source link

Functions Switch Plug, No Power Info Returned #13

Open arrmo opened 3 years ago

arrmo commented 3 years ago

Hi,

I have a switch, running v3.1 of the protocol (based on the scan). I can connect to it, and get the switch state ... but executing tuyapower.devicePrint sometimes turns the switch off and back on (not good!), and the output is,

 Device ******* at IP key ******* protocol 3.1:
    Switch On: True
    Power (W): -99.000000
    Current (mA): -99.000000
    Voltage (V): -99.000000
    Projected usage (kWh):  Day: -2.376000  Week: -16.632000  Month: -72.072000

But, using tuyacli, I do get correct info. Any thoughts how to correct / debug this?

Thanks!

jasonacox commented 3 years ago

Can you give me the tuya-clicommand you are using? I assume something like..

tuya-cli get --ip 10.0.0.10 --id 03200160dc4f2216f984 --key 01234567890 -a

If it is a 3.1 device, you should be able to grab the status from it without a key. What do you see with python3 -m tuyapower ?

arrmo commented 3 years ago

Can you give me the tuya-clicommand you are using? I assume something like..

Yep, exactly like that πŸ˜„,

./node_modules/@tuyapi/cli/cli.js get --id ******** --key ******** --ip 192.168.2.147 -a

And, I get,

{
  devId: '********',
  dps: {
    '1': false,
    '2': false,
    '9': 0,
    '10': 0,
    '18': 0,
    '19': 0,
    '20': 1237,
    '21': 1,
    '22': 1189,
    '23': 14239,
    '24': 15400,
    '25': 1170
  }
}

It says the interface is 3.1, but I can't get the status without a key. If I run, ```python3 -m tuyapower```,

Scanning on UDP ports 6666 and 6667 for devices (15 retries)...

FOUND Device [Valid payload]: 192.168.2.147 ID = ****, product = keyjcr45yfptp7h7, Version = 3.1 Stats: on=False [Power data unavailable]



Thoughts?

Thanks!
jasonacox commented 3 years ago

That's fascinating. I see the problem. It is sending a payload back that looks more like a 3.3 device. Because it announces itself as 3.1 device, tuyapower is unable to pull the power data. A 3.1 plug would send back something like this:

Response Data: {'devId': '03200160dc4f2216f984', 'dps': {'1': True, '2': 0, '4': 473, '5': 561, '6': 1217}}

The tuyapower logic is trying to parse the response with this logic:

            if data:
                dps = data["dps"]
                sw = dps["1"]
                if vers == "3.3" and ("19" in dps.keys()):
                    w = float(dps["19"]) / 10.0
                    mA = float(dps["18"])
                    V = float(dps["20"]) / 10.0
                    key = "OK"
                elif "5" in dps.keys():
                    w = float(dps["5"]) / 10.0
                    mA = float(dps["4"])
                    V = float(dps["6"]) / 10.0
                    key = "OK"
                else:
                    key = "Power data unavailable"

Based on your payload, none of those would match (hence "Power data unavailable"). I don't know the dps mappings for your device. It seems to be a non-standard Tuya plug device. That could also explain why it has the unexpected power off situation (evidence that the simple tuyapower status query is crashing its api handler = not good!)

What type of device is it? Also, does the Device ID happen to have 22 characters?

One curious thing to try, does it respond if you force a 3.3 protocol query?

./plugpower.py 03200160dc4f2216f984 10.0.1.1 01234567890 3.3

Also, you can try to use tinytuya to poll the device:

import tinytuya

d = tinytuya.Device('03200160dc4f2216f984','10.0.1.1','01234567890')
d.status()  # test 3.1
d.set_version(3.3)
d.status()  # test 3.3
arrmo commented 3 years ago

It seems to be a non-standard Tuya plug device.

But it matches (closely) to this (and what I find at the Tuya site), https://tasmota.github.io/docs/TuyaMCU/#power-monitoring-plug ... so really non-standard? BTW, it's a dual device, so DP IP 1 and 2 are used (for the two switches).

sw = dps["1"]

Based on the link I sent above, this field should show on/off status for the switch, no? And it does match (status) to the cli output.

does the Device ID happen to have 22 characters?

Nope, 20.

One curious thing to try, does it respond if you force a 3.3 protocol query?

After quite a while, but it toggles the switch off and back on (twice), then the data below. BTW, forcing 3.1, same toggle (only once), and the same data.

 ERROR: Timeout polling device

TuyaPower (Tuya Power Stats) [0.0.25] tinytuya [1.0.4]

Device ******************** at 192.168.2.147 key **************** protocol 3.3:
    Response Data: ERROR: Timeout polling device
    Switch On: False
    Power (W): -99.000000
    Current (mA): -99.000000
    Voltage (V): -99.000000
    Projected usage (kWh):  Day: -2.376000 Week: -16.632000  Month: -72.072000

{ "datetime": "2020-11-21T11:54:31Z", "switch": "False", "power": "-99", "current": "-99", "voltage": "-99" }
d.status()  # test 3.1

I get,

ConnectionResetError: [Errno 104] Connection reset by peer

And,

d.status()  # test 3.3

Here, I get,

ValueError: Data must be aligned to block boundary in ECB mode

Thoughts? And thanks!

arrmo commented 3 years ago

FYI, just bought a couple more switches (these ones! https://www.amazon.com/gp/product/B07CVPKD8Z/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1). Python code works, and so does plugpower.py ... if I force 3.3. If not, it errors out, unexpected status().

Thanks!

jasonacox commented 3 years ago

Ok, for the 2 plug switch that broadcasts the 3.1, I'm not really find any any good information to help us. However, since it behaves like a 3.3 device, the mapping may be the similar (see https://github.com/jasonacox/tinytuya#data-points---dps-table).

DP ID Function Point Type Range Units
1 Switch 1 bool 0-1
2 Switch 2 bool 0-1
9 Countdown 1 integer 0-86400 s
10 Countdown 2 integer 0-86400 s
18 Current integer 0-30000 mA
19 Power integer 0-50000 W
20 Voltage integer 0-5000 V
21 Test Bit integer 0-5 n/a
22 Voltage coe integer 0-1000000
23 Current coe integer 0-1000000
24 Power coe integer 0-1000000
25 Electricity coe integer 0-1000000

The problem is that I have never seen a 3.1 device that maps to this. I could add code to handle that response, but first I need to figure out why tuya-cli is able to pull back status but we are not getting it with tinytuya.

For those new switches, those should be 3.3 devices so you will need to set the version before running status().

d = tinytuya.Device('03200160dc4f2216f984','10.0.1.1','01234567890')
d.set_version(3.3)
d.status()  # test 3.3
arrmo commented 3 years ago

OK, a bit more digging - and I apologize, I may have confused you (or at least I confused myself ... 🀣). It is looking to me like a 3.1 device, more on that below. And yes, I do think the mapping is as you note. I found this as well (https://tasmota.github.io/docs/TuyaMCU/#power-monitoring-plug), and it seems to match to the output from my device.

So mapping aside (I think that needs to get adjusted), I have tried a few different approaches, 1) @tuyapi/cli, this seems to work, either with 3.1 or 3.3 (set through --protocol-version) ... but it needs the key set in either case (bug in the code perhaps?). It doesn't do any sort of mapping, but the dps json is matching the mapping we both found.

2) plugpower.py, it works more than it looks like πŸ˜†. I say that because here is the output,

 ERROR: Power data unavailable

TuyaPower (Tuya Power Stats) [0.0.25] tinytuya [1.0.4]

Device ******************** at 192.168.2.147 key 0123456789abcdef protocol 3.1:
    Response Data: {'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0, '18': 501, '19': 610, '20': 1213, '21': 1, '22': 1189, '23': 14239, '24': 15400, '25': 1170}}
    Switch On: True
    Power (W): -99.000000
    Current (mA): -99.000000
    Voltage (V): -99.000000
    Projected usage (kWh):  Day: -2.376000 Week: -16.632000  Month: -72.072000

{ "datetime": "2020-11-22T00:13:50Z", "switch": "True", "power": "-99", "current": "-99", "voltage": "-99" }

The Response Data is correct! I think I missed this before. It's just not being interpreted correctly. And FYI, I have a 60W light bulb connected, for testing ... matches to field 19! Voltage is 20, and current is 18. So I think the ERROR: Power data unavailable message is just a mapping / interpretation thing. But ... I do sometimes see Unexpected status() payload=b'json obj data unvalid'. Not sure what that means, but more on that below. Bottom line, it seems like 3.1 is correct (as reported), and working.

3) Trying the Python code, setting (only) ID and IP address, with version at 3.1 ... it works ... mostly πŸ˜‰. Saying that because consecutive status retrieve looks like this,

{'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0, '18': 501, '19': 611, '20': 1218, '21': 1, '22': 1189, '23': 14239, '24': 15400, '25': 1170}}
>>> d.status()
{'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0}}
>>> d.status()
{'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0, '18': 500, '19': 607, '20': 1218, '21': 1, '22': 1189, '23': 14239, '24': 15400, '25': 1170}}
>>> d.status()
Unexpected status() payload=b'json obj data unvalid'
b'json obj data unvalid'
>>> d.status()
{'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0}}
>>> d.status()
{'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0, '18': 500, '19': 607, '20': 1204, '21': 1, '22': 1189, '23': 14239, '24': 15400, '25': 1170}}

So ... sometimes it works, other times it's missing data (timeout too fast?), and other times ... the data invalid message (also timeout perhaps? Just a thought!).

But, the other gotcha in all this - sometimes (not real consistent), getting status turns the switch output off and back on. That's not good of course, not really usable (it will be connected to a PC). Thoughts on that?

Does this make a bit more sense?

BTW, is the access really local / direct, not through the cloud? Asking because if it is, as we try fixes, I can put it in a loop, let it run overnight for testing - if it's really local (i.e. don't want to hammer the cloud with requests over a long period of time). And FYI, not sure if you know this or not, but the key does seem to get changed sometimes.

Thanks!

jasonacox commented 3 years ago

Awesome! That helps. I will add the logic to do the correct mapping. However, the unexpected switch power cycle concerns me. Did you see that behavior doing multiple tuya-cli calls?

I found one minor difference between tuya-cli and tinytuya related to how the payload is formed. I've updated tinytuya (1.0.5). I would be curious if this helps.

git clone https://github.com/jasonacox/tinytuya.git
cd tinytuya
python3
import tinytuya

tinytuya.version
d = tinytuya.Device('03200160dc4f2216f984','10.0.1.1','01234567890')
d.status() 

The Local Key for the device will change if you remove and add the device to the SmartLife or TuyaSmart app. It is odd that it is changing on you if you are not doing that. Have you checked to see if there is a firmware update for the device?

As far as your question about the cloud, this is all local traffic. Both tinytuya and tuyapower only talk directly to the Tuya device on the local network.

arrmo commented 3 years ago

Have you checked to see if there is a firmware update for the device?

You and I are in sync ... πŸ˜†. I had raised a ticket to Tuya, and just got a reply, Hi dear,I have pushed the latest version of 1.1.2 for you. You update. Thank you for your support and wish you a happy life.

This is an update from 1.0.4 to 1.1.2 and ... πŸ₯ ... fixed! Now the cli wizard shows 3.3, and plugpower.py outputs,

Device ******************** at 192.168.2.147 key **************** protocol 3.3:
    Response Data: {'devId': '********************', 'dps': {'1': True, '2': True, '9': 0, '10': 0, '18': 501, '19': 612, '20': 1218, '21': 1, '22': 1189, '23': 14239, '24': 15400, '25': 1170}}
    Switch On: True
    Power (W): 61.200000
    Current (mA): 501.000000
    Voltage (V): 121.800000
    Projected usage (kWh):  Day: 1.468800 Week: 10.281600  Month: 44.553600

{ "datetime": "2020-11-22T03:25:13Z", "switch": "True", "power": "61.2", "current": "501.0", "voltage": "121.8" }

So that was the underlying root cause, or at least all in sync with 3.3. Thanks for the help and pointers!

The Local Key for the device will change if you remove and add the device to the SmartLife or TuyaSmart app

I had updated the app (to a Beta that popped up, thinking that plus messed up firmware was causing the change.

As far as your question about the cloud, this is all local traffic. Both tinytuya and tuyapower only talk directly to the Tuya device on the local network.

Perfect! I'll still set up the loop (tomorrow πŸ˜„), see if it's all clean. BTW, is there a worry that the online service will go away (they seem to be heading to paid offerings?)? Just don't want to lose access to my devices.

Hmmm ... OK, this is odd. Now in Python (not updated from repository) ... d.status() fails, with ConnectionResetError: [Errno 104] Connection reset by peer. Make any sense? OK, seems like I have to use OutletDevice, not "just" Device ... sound right?

Thanks again for the digging!

jasonacox commented 3 years ago

Thanks for the update! I'm glad it is working. I'll leave this issue open for other who are having similar struggles. I'll also add a note to the README to recommend upgrading firmware if there is unexpected behavior.

arrmo commented 3 years ago

BTW, as I work through getting the queries fully working here - have you thought about including the equivalent of the cli "wizard" (get key information) in your tuya library? Just to avoid neeting node / npm, just for that part of the process? Not a big deal, just curious.

Thanks!

jasonacox commented 3 years ago

Challenge accepted! πŸ‘ I'll upload something soon.

jasonacox commented 3 years ago

Here is the start of a python version of tuya-cli wizard: https://github.com/jasonacox/tinytuya/blob/master/wizard.py

Test it out and let me know if it works for you. Fair warning - it doesn't yet work with python2.7.

python3 wizard.py
arrmo commented 3 years ago

OMG! That is so awesome - thanks!!!

Tried it out here, works perfectly. I really do like how it saves the information to local files also, great touch. I thought for a minute it was missing a couple devices (scanning local network), but then remembered I had them unplugged as I was moving devices between rooms (i.e. nut behind the wheel issue ... LOL).

This really is awesome. Nice to get rid of npm / node modules, those always cause me grief πŸ˜†.

OK, last "important" question - how do I buy you a coffee / beer / beverage of your choice? Only seems right to me.

Thanks again.

arrmo commented 3 years ago

BTW, one very minor thing ... devices and tinytuya (json) save is great. Perhaps also save the output from "Polling local devices..."? Just because that provides the IP address mapping.

Thanks again!

jasonacox commented 3 years ago

Thanks for the kind words and offer Russell! Hope to take you up on that one of these days.

For the polling data, I originally didn't want to save the IP address in the devices.json file since the IP can change frequently (DHCP lease). However, this is a good suggestion. I added a snapshot.json file that is created at the end of the Wizard process. It is timestamped but has all the device details, IP address and states at that time. Hopefully this works. I'm testing the changes and added the wizard into the main module for easy use w/o downloading the separate wizard.py file. I'll release it soon.

arrmo commented 3 years ago

Hope to take you up on that one of these days.

Absolutely! The offer holds πŸ˜ƒ.

I'll release it soon.

No panic! Sounds good though. And agreed on DHCP - but I also have address reservations for all my HW, so that makes it handy to have the IP address(s).

Thanks again!