fishbigger / TapoP100

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

How to get the current value of the timer #113

Closed Konubinix closed 1 year ago

Konubinix commented 1 year ago

I just received a P100 and this library works like a charm.

I could not find a way though to read the current value of the timer.

Say I run this once

p.turnOffWithDelay(10)

I can see the plug turning on after 10 seconds.

Aftewards, I can setup another timer.

Yet, when a timer is already set, I get this error.

p.turnOffWithDelay(10)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[30], line 1
----> 1 p.turnOffWithDelay(10)

File ~/.local/lib/python3.11/site-packages/PyP100/PyP100.py:355, in P100.turnOffWithDelay(self, delay)
    353 if ast.literal_eval(decryptedResponse)["error_code"] != 0:
    354         errorCode = ast.literal_eval(decryptedResponse)["error_code"]
--> 355         errorMessage = self.errorCodes[str(errorCode)]
    356         raise Exception(f"Error Code: {errorCode}, {errorMessage}")

KeyError: '-1802'

I understand that the plug is telling me that a timer is already setup and that this error code is not already handled by this library.

I wonder if anyone has found out a way to ask the plug the value of the currently running timer.

Konubinix commented 1 year ago

My android phone is rooted. I can run tcpdump to seet the traffic when I create and stop a timer using my phone.

With a little hand, I think I can reverse engineer the messages sent and received and submit a PR.

Konubinix commented 1 year ago

To do so, I suppose I need to find where the session key is stored inside the phone and decrypt the messages that are sent. Am I right?

Konubinix commented 1 year ago

By decompiling the apk, I could find out the method "get_countdown_rules".

I copy/pasted getDeviceInfo and created getCountDownRules

def getCountDownRules(self):
    URL = f"http://{self.ipAddress}/app?token={self.token}"
    Payload = {
        "method": "get_countdown_rules",
    }
    headers = {"Cookie": self.cookie}
    EncryptedPayload = self.tpLinkCipher.encrypt(json.dumps(Payload))
    SecurePassthroughPayload = {
        "method": "securePassthrough",
        "params": {
            "request": EncryptedPayload
        }
    }
    r = self.session.post(URL, json=SecurePassthroughPayload, headers=headers)
    decryptedResponse = self.tpLinkCipher.decrypt(
        r.json()["result"]["response"])
    return json.loads(decryptedResponse)
p.getCountDownRules()
{'result': {'enable': True,
  'countdown_rule_max_count': 1,
  'rule_list': [{'enable': True,
    'id': 'C1',
    'delay': 86880,
    'remain': 77602,
    'desired_states': {'on': False}}]},
 'error_code': 0}
Konubinix commented 1 year ago

There is also remove_countdown_rules and edit_countdown_rules, but I could not find out what parameter to give them. I cannot find where they are called in the decompiled code. Any advice?

Also, I cannot find a key on the phone, because the key is generated on the fly when the application starts. So I guess trying to use the application to find out the format of the messages is more complicated than I guessed.

I could try to dump the memory of the program and look for something that could look like a key, but that appears to me as a very time consuming task.

Konubinix commented 1 year ago

Closing this issue, since there is now the PR #114 that deals with it