fishbigger / TapoP100

A module for controlling the TP-Link Tapo P100 Plugs
MIT License
567 stars 139 forks source link

P110.getEnergyUsage() returns error after recent P110 firmware update #80

Open vladak opened 1 year ago

vladak commented 1 year ago

A sibling to #76 - after firmware update to 1.0.16, the get_enery_usage() request returns error.

The code looks like this:

from PyP100 import PyP110

p110 = PyP110.P110(hostname, username, password)
p110.handshake()
p110.login()

print(f"device info: {p110.getDeviceInfo()}")
print(f"energy usage: {p110.getEnergyUsage()}")

in case of P110 running older firmware (1.0.7), getEnergyUsage() produces expected reply. When the program is run against P110 with the newer firmware (using code patched with #77 so that login() works) it reports this (with sensitive values redacted with ...):

device info: {'result': {'device_id': '...', 'fw_ver': '1.0.16 Build 220624 Rel.171733', 'hw_ver': '1.0', 'type': 'SMART.TAPOPLUG', 'model': 'P110', 'mac': '...', 'hw_id': '...', 'fw_id': '00000000000000000000000000000000', 'oem_id': '...', 'ip': '172.40.0.8', 'time_diff': 60, 'ssid': '...', 'rssi': -31, 'signal_level': 3, 'latitude': 0, 'longitude': 0, 'lang': '...', 'avatar': 'plug', 'region': '...', 'specs': '', 'nickname': '...', 'has_set_location_info': False, 'device_on': True, 'on_time': 200, 'default_states': {'type': 'last_states', 'state': {}}, 'overheated': False}, 'error_code': 0}
energy usage: {'error_code': -1003}

Looking into ERROR_CODES, -1003 corresponds to 'JSON formatting error'. Printing the payload before encryption looks to be correctly formed JSON:

{"method": "get_energy_usage", "requestTimeMils": 1664568446147}

Similarly for the request payload:

{"method": "securePassthrough", "params": {"request": "rg13FGq/M0s82AJcBjUKxQq3V4A7PkEPh1fk/NYsJ2L5BIhD7J+UqJyIWSuSj58Jc0VNs50Pasa0ObHr5Cq5IA=="}}

and jq seems to agree.

vladak commented 1 year ago

Experimenting a bit, when changing get_energy_usage in the payload to something else (get_energy_info), the response changes to:

{'error_code': -1002}

P100.py contains:

ERROR_CODES = {                                                                 
        "0": "Success",                                                         
        "-1010": "Invalid Public Key Length",                                   
        "-1012": "Invalid terminalUUID",                                        
        "-1501": "Invalid Request or Credentials",                              
        "1002": "Incorrect Request",                                            
        "-1003": "JSON formatting error "                                       
}

so the "1002" should probably be changed to "-1002". Anyway, this means that get_energy_usage is likely correct even in the new firmware.

johnflan commented 1 year ago

Using the proposed change #77 and querying my P110 with the example you listed above i get

energy usage: {'result': {'today_runtime': 1366, 'month_runtime': 1648, 'today_energy': 230, 'month_energy': 230, 'local_time': '2022-09-30 22:46:23', 'past24h': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 47, 49, 52, 41], 'past30d': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230], 'past1y': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230], 'past7d': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 47, 49, 52, 41, 0]], 'current_power': 55598}, 'error_code': 0}

it seems to be working fine?

vladak commented 1 year ago

It does not in my case. To be sure, I cloned https://github.com/tking2/TapoP100.git (the main branch, used for the PR #77) and:

$ git log --pretty=oneline -2
315929a1e4e79bb622e061dacaca512d4d9cd597 (HEAD -> main, origin/main, origin/HEAD) chg: Pre-emptively move to using cookies
132ae9b4893584a023be69ae497560a928b1c24e fix: Improve parsing of cookie, thus removing trailing ;
$ python3 -m venv env
$ . ./env/bin/activate
$ which pip
/Users/me/Pi/TapoP100/env/bin/pip
$ pip install -e .
Obtaining file:///Users/me/Pi/TapoP100
  Preparing metadata (setup.py) ... done

...

$ python3
>>> from PyP100 import PyP110
>>> hostname = "..."
>>> username = "..."
>>> password = "..."
>>> p110 = PyP110.P110(hostname, username, password)
>>> p110.handshake()
>>> p110.login()
>>> p110.getDeviceInfo()
{'result': {'device_id': ...
>>> p110.getEnergyUsage()
{'error_code': -1003}
johnflan commented 1 year ago

I'm running 1.0.16 Build 220624 Rel. 171733 on a P110 (uk/ireland)

Pulling from the @tking2 repo (now merged)

FROM python:3.11-rc-alpine3.15
RUN apk --no-cache add git
WORKDIR /usr/src/app
RUN git clone https://github.com/tking2/TapoP100.git && cd TapoP100 && python setup.py install
COPY script.py .
CMD python script.py
p110 = PyP110.P110("192.168.0.214", "username", "passed")
p110.handshake()
p110.login()
payload = json.loads(str(p110.getEnergyUsage()).replace("\'", "\"" ))
milliwatts = payload['result']['current_power']

Thats currently working for me

Interestingly the JSON returned from the device uses single quotes instead of double quotes. I do a find and replace before parsing the payload. It might be a nice enhancement to have getters on the object returned by getEnergyUsage to extract metrics.

tking2 commented 1 year ago

Hi @vladak, I can't seem to replicate this either unfortunately. Updated my P110 to the latest and matching firmware as you have, and get the intended response. All looks OK with your payload, correctly formatted JSON. Still persists after a reboot of the plug?

Personally I'm not sure of the significance of the error codes, but as each is unique might be a better idea just to abs the code and treat all as positive integers

Maybe try replacing requestTimeMils with a hardcoded value of 0 just to discount as much as possible (P110/P100 does not seem to care about having a valid millisecond time in this field.

@johnflan - The JSON returned form the device is a python dict. Therefore you don't need to cast to a string and reload, simply doing the following would suffice:

p110 = PyP110.P110("192.168.0.214", "username", "passed")
p110.handshake()
p110.login()
payload = p110.getEnergyUsage()
milliwatts = payload['result']['current_power']

Building getters could be an idea however, which would also allow you to convert into kWh or any other unit, which would also help to satisfy #78

bacheshrew commented 1 year ago

I think historical data is cleared by the firmware update, but should there be empty fields (or zeros) in the data returned? Using the proposed change #77 and calling getEnergyUsage() I get two different results, depending on the firmware version.

For devices running the older firmware (v1.0.16 Build 220624 Rel.171733), I get 'result' which shows historical data populated as: {'today_runtime': 1213, 'month_runtime': 42966, 'today_energy': 1414, 'month_energy': 56071, 'local_time': '2022-10-22 20:13:00', 'past24h': [72, 42, 167, 134, 54, 37, 72, 88, 37, 38, 85, 77, 40, 62, 98, 105, 65, 87, 62, 36, 88, 74, 46, 29], 'past30d': [1916, 1766, 1825, 1788, 2050, 2037, 1919, 2081, 1858, 1783, 2044, 1922, 1852, 1907, 1790, 1762, 1851, 1896, 2072, 2046, 1796, 1848, 1941, 1940, 1839, 1894, 1792, 1765, 1677, 1414], 'past1y': [0, 0, 0, 0, 0, 0, 0, 0, 0, 29890, 56387, 40689], 'past7d': [[75, 60, 65, 54, ....<snipped to save space>....]], 'current_power': 129899}, 'error_code': 0}

For devices running the newer firmware (v1.1.2 Build 220930 Rel.144500), I get 'result' populated with no historical data fields as: 'result': {'today_runtime': 165, 'month_runtime': 9314, 'today_energy': 33, 'month_energy': 1891, 'local_time': '2022-10-22 20:13:01', 'electricity_charge': [0, 0, 0], 'current_power': 12173}, 'error_code': 0}

tking2 commented 1 year ago

Hi @bacheshrew, I'll try to test if this has changed on mine soon.

I'd suggest raising a new issue for this, these are two different issues

tnmendes commented 1 year ago

Hey guys, Tapo is using a new way to get energy data from the P110.

So now to get energy data for 12 months in one year you can do this:

{ "method": "get_energy_data", "params": { "end_timestamp": 1667260800, "interval": 43200, "start_timestamp": 1640995200 } }

Response: { "energy_data": { "local_time": "2022-10-22 20:17:12", "data": [ 32148, 36482, 28707, 14642, 16811, 9126, 12986, 7617, 13833, 14733, 0, 0 ], "start_timestamp": 1640995200, "end_timestamp": 1667260800, "interval": 43200 } }

Interval is in minutes and from what I saw in my tests the minimum interval is 60 minutes

bacheshrew commented 1 year ago

@tking2 New issue (#86) raised. Thanks for testing this

realzoulou commented 1 year ago

Tested successfully with PyP100==0.1.2 and a P110 device (no load on it) with FW 1.1.2 Build 220930 Rel.144500

>>> from PyP100 import PyP110
>>> p110 = PyP110.P110("192.168.178.43","zoidl@web.de","Jimmy,Fly,Luke2016")
>>> p110.handshake()
>>> p110.login()

>>> print(f"device info: {p110.getDeviceInfo()}")

device info: {'result': {'device_id': '80222C6333A86CD79FCD8057FC20EE341FF0AA16', 'fw_ver': '1.1.2 Build 220930 Rel.144500', 'hw_ver': '1.0', 'type': 'SMART.TAPOPLUG', 'model': 'P110', 'mac': '34-60-F9-56-86-A7', 'hw_id': '2FB30EF5BF920C44099401D396C6B55B', 'fw_id': '00000000000000000000000000000000', 'oem_id': '18BDC6C734AF8407B3EF871EACFCECF5', 'ip': '192.168.178.43', 'time_diff': 60, 'ssid': '<removed>', 'rssi': -40, 'signal_level': 3, 'latitude':<removed>, 'longitude':<removed>, 'lang': 'de_DE', 'avatar': 'plug', 'region': 'Europe/Berlin', 'specs': '', 'nickname': '<removed>', 'has_set_location_info': True, 'device_on': True, 'on_time': 263685, 'default_states': {'type': 'last_states', 'state': {}}, 'overheated': False, 'power_protection_status': 'normal'}, 'error_code': 0}

>>> print(f"energy usage: {p110.getEnergyUsage()}")

energy usage: {'result': {'today_runtime': 1075, 'month_runtime': 4397, 'today_energy': 0, 'month_energy': 0, 'local_time': '2022-11-01 17:55:41', 'electricity_charge': [0, 0, 0], 'current_power': 0}, 'error_code': 0}
>>>
ErnstAhlers commented 1 year ago

Tested successfully with PyP100==0.1.2 and a P110 device (no load on it) with FW 1.1.2 Build 220930 Rel.144500

My experience with the same PyP100 version on a P110 without load and same firmware differs:

>>> from PyP100 import PyP110
>>> p110 = PyP110.P110("192.168.6.43","someone@somewhere.de","totalgeheim")
>>> p110.handshake()
>>> p110.login()
>>> print(f"device info: {p110.getDeviceInfo()}")
device info: {'result': {'device_id': '802236EAA26BB507FA119D2EB9E2186820564901', 'fw_ver': '1.1.2 Build 220930 Rel.144500', 'hw_ver': '1.0', 'type': 'SMART.TAPOPLUG', 'model': 'P110', 'mac': '9C-A2-F4-7A-A7-3B', 'hw_id': '2FB30EF5BF920C44099401D396C6B55B', 'fw_id': '00000000000000000000000000000000', 'oem_id': '18BDC6C734AF8407B3EF871EACFCECF5', 'ip': '192.168.6.43', 'time_diff': 60, 'ssid': 'removed', 'rssi': -38, 'signal_level': 3, 'latitude': 515900, 'longitude': 103900, 'lang': 'de_DE', 'avatar': 'sound', 'region': 'Europe/Berlin', 'specs': '', 'nickname': 'U3dpdGNoLVRyb2NrbmVy', 'has_set_location_info': True, 'device_on': True, 'on_time': 25144, 'default_states': {'type': 'last_states', 'state': {}}, 'overheated': False, 'power_protection_status': 'normal'}, 'error_code': 0}
>>> print(f"energy usage: {p110.getEnergyUsage()}")
energy usage: {'error_code': -1003}

Initially it works. The error shows up after some runtime if the P110 doesn't have an internet connection. Any suggestions?

Updated 7Nov22: After some initial fails the P110 seems to work stable when regularly polled by Node Red. Has anyone else seen intermittent failures with this firmware/software combo?

6661620a commented 1 year ago

Tested successfully with PyP100==0.1.2 and a P110 device (no load on it) with FW 1.1.2 Build 220930 Rel.144500

My experience with the same PyP100 version on a P110 without load and same firmware differs:

>>> from PyP100 import PyP110
>>> p110 = PyP110.P110("192.168.6.43","someone@somewhere.de","totalgeheim")
>>> p110.handshake()
>>> p110.login()
>>> print(f"device info: {p110.getDeviceInfo()}")
device info: {'result': {'device_id': '802236EAA26BB507FA119D2EB9E2186820564901', 'fw_ver': '1.1.2 Build 220930 Rel.144500', 'hw_ver': '1.0', 'type': 'SMART.TAPOPLUG', 'model': 'P110', 'mac': '9C-A2-F4-7A-A7-3B', 'hw_id': '2FB30EF5BF920C44099401D396C6B55B', 'fw_id': '00000000000000000000000000000000', 'oem_id': '18BDC6C734AF8407B3EF871EACFCECF5', 'ip': '192.168.6.43', 'time_diff': 60, 'ssid': 'removed', 'rssi': -38, 'signal_level': 3, 'latitude': 515900, 'longitude': 103900, 'lang': 'de_DE', 'avatar': 'sound', 'region': 'Europe/Berlin', 'specs': '', 'nickname': 'U3dpdGNoLVRyb2NrbmVy', 'has_set_location_info': True, 'device_on': True, 'on_time': 25144, 'default_states': {'type': 'last_states', 'state': {}}, 'overheated': False, 'power_protection_status': 'normal'}, 'error_code': 0}
>>> print(f"energy usage: {p110.getEnergyUsage()}")
energy usage: {'error_code': -1003}

Initially it works. The error shows up after some runtime if the P110 doesn't have an internet connection. Any suggestions?

Updated 7Nov22: After some initial fails the P110 seems to work stable when regularly polled by Node Red. Has anyone else seen intermittent failures with this firmware/software combo?

Hey @ErnstAhlers , how did you manage to solve the issue? I am running the P110 ("fw_ver": "1.1.5 Build 221109 Rel.125117") without internet connection and I am getting the same error when trying to query the energy usage {'error_code': -1003}

ErnstAhlers commented 1 year ago

Hi 6661620a, I didn't resolve the issue but switched to a different make that works more reliably with Node Red. Best Ernst

remiheens commented 1 year ago

Hi,

I've the same issue with my P110 , Firmware version : 1.1.6 Build 221114 Rel.203339

I'm getting error 1003 when call get_energy_data {'error_code': -1003}

Do you have some workaround ?