jasonacox / tinytuya

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

Smart Keypad - ValueError: invalid padding length byte #438

Open mrbrdo opened 9 months ago

mrbrdo commented 9 months ago

I am interested in trying to add support for a Smart Keypad/Card+PIN device (product id s7qamihym60z5jro). I am aware it's not supported at this time. Would like to get some help with getting basic status / protocol / message encryption working and then I might be able to implement the functionality myself. When trying to do status(), I get the following error: ValueError: invalid padding length byte. How could I successfully decode the message?

Here is the debug output:

DEBUG:status() entry (dev_type is default)
DEBUG:final payload_dict for 'MYDEVID' ('v3.3'/'default'): {1: {'command': {'gwId': '', 'devId': '', 'uid': '', 't': ''}}, 7: {'command': {'devId': '', 'uid': '', 't': ''}}, 8: {'command': {'gwId': '', 'devId': ''}}, 9: {'command': {'gwId': '', 'devId': ''}}, 10: {'command': {'gwId': '', 'devId': '', 'uid': '', 't': ''}}, 13: {'command': {'devId': '', 'uid': '', 't': ''}}, 16: {'command': {'devId': '', 'uid': '', 't': ''}}, 18: {'command': {'dpId': [18, 19, 20]}}, 64: {'command': {'reqType': '', 'data': {}}}}
DEBUG:building command 10 payload=b'{"gwId":"MYDEVID","devId":"MYDEVID","uid":"MYDEVID","t":"1702944031"}'
DEBUG:sending payload
DEBUG:payload encrypted=b'000055aa000000010000000a00000088426805b5e7556886e67a7652b5bfa0d5585c80e7e213a9fd044ca73a85b24cc9ceb82602f298e8d2284261a322a03cd23630dd9c551b275eba920a8a288e19d7f1cc30c74556c9da6e69bfbde5341fc0585c80e7e213a9fd044ca73a85b24cc92a8dd8946f6c90dcd4faff0b1e79b606fd15c744626b1b2eff4deedbffcdf03b759660d70000aa55'
DEBUG:received data=b'000055aa000000010000000a0000002c00000001ddc430e4f6cb4f63398bbc4de08374c7e3891724694edf79f47e09dbaf36eb5ac9412c390000aa55'
DEBUG:received message=TuyaMessage(seqno=1, cmd=10, retcode=1, payload=b'\xdd\xc40\xe4\xf6\xcbOc9\x8b\xbcM\xe0\x83t\xc7\xe3\x89\x17$iN\xdfy\xf4~\t\xdb\xaf6\xebZ', crc=3376491577, crc_good=True, prefix=21930, iv=None)
DEBUG:raw unpacked message = TuyaMessage(seqno=1, cmd=10, retcode=1, payload=b'\xdd\xc40\xe4\xf6\xcbOc9\x8b\xbcM\xe0\x83t\xc7\xe3\x89\x17$iN\xdfy\xf4~\t\xdb\xaf6\xebZ', crc=3376491577, crc_good=True, prefix=21930, iv=None)
DEBUG:decode payload=b'\xdd\xc40\xe4\xf6\xcbOc9\x8b\xbcM\xe0\x83t\xc7\xe3\x89\x17$iN\xdfy\xf4~\t\xdb\xaf6\xebZ'
DEBUG:decrypting=b'\xdd\xc40\xe4\xf6\xcbOc9\x8b\xbcM\xe0\x83t\xc7\xe3\x89\x17$iN\xdfy\xf4~\t\xdb\xaf6\xebZ'
DEBUG:incomplete payload=b'\xdd\xc40\xe4\xf6\xcbOc9\x8b\xbcM\xe0\x83t\xc7\xe3\x89\x17$iN\xdfy\xf4~\t\xdb\xaf6\xebZ' (len:32)
Traceback (most recent call last):
  File "tinytuya/core.py", line 1386, in _decode_payload
    payload = cipher.decrypt(payload, False)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "tinytuya/core.py", line 325, in decrypt
    raw = self._unpad(raw, verify_padding)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "tinytuya/core.py", line 286, in _unpad
    raise ValueError("invalid padding length byte")
ValueError: invalid padding length byte
DEBUG:ERROR Unexpected Payload from Device - 904 - payload: null
DEBUG:status() received data={'Error': 'Unexpected Payload from Device', 'Err': '904', 'Payload': None}
DEBUG:Status request returned an error, is version 3.3 and local key b'MYLOCALKEY' correct?
set_status() result {'Error': 'Unexpected Payload from Device', 'Err': '904', 'Payload': None}

I'm happy to run any further commands and share more debug output.

uzlonewolf commented 9 months ago

That means it can't decrypt the response because the local key is wrong. I'd run the wizard again to make sure you have the latest key.

mrbrdo commented 9 months ago

@uzlonewolf thanks, that fixed it, however I obtained the key several times before through the Tuya IoT API, and that one seems to be in a different format? This one from tinytuya seems like HEX format, but the one from the API definitely isn't in HEX. Do you think there is any chance of using this device from tinytuya at the moment? For example unlock function and temporary code function.

{
  "result": {
    "category": "mk",
    "functions": [
      {
        "code": "unlock_method_create",
        "desc": "{}",
        "name": "添加开门方式",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_method_delete",
        "desc": "{}",
        "name": "删除开门方式",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_method_modify",
        "desc": "{}",
        "name": "修改开门方式",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "synch_method",
        "desc": "{}",
        "name": "同步开门方式(全量同步)",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "temporary_password_creat",
        "desc": "{}",
        "name": "添加临时密码",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "temporary_password_delete",
        "desc": "{}",
        "name": "删除临时密码",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "temporary_password_modify",
        "desc": "{}",
        "name": "修改临时密码",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "remote_no_pd_setkey",
        "desc": "{}",
        "name": "设置免密远程开门密钥",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "remote_no_dp_key",
        "desc": "{}",
        "name": "新免密远程开门-带密钥",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_password_kit",
        "desc": "{}",
        "name": "普通密码开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_card_kit",
        "desc": "{}",
        "name": "卡片开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_temporary_kit",
        "desc": "{}",
        "name": "临时密码开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_phone_remote_kit",
        "desc": "{}",
        "name": "手机远程开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "lock_alarm_kit",
        "desc": "{}",
        "name": "告警记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "closed_opened_kit",
        "desc": "{}",
        "name": "开合状态",
        "type": "Raw",
        "values": "{}"
      }
    ],
    "status": [
      {
        "code": "unlock_method_create",
        "name": "添加开门方式",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_method_delete",
        "name": "删除开门方式",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_method_modify",
        "name": "修改开门方式",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "synch_method",
        "name": "同步开门方式(全量同步)",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "temporary_password_creat",
        "name": "添加临时密码",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "temporary_password_delete",
        "name": "删除临时密码",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "temporary_password_modify",
        "name": "修改临时密码",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "remote_no_pd_setkey",
        "name": "设置免密远程开门密钥",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "remote_no_dp_key",
        "name": "新免密远程开门-带密钥",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_password_kit",
        "name": "普通密码开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_card_kit",
        "name": "卡片开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_temporary_kit",
        "name": "临时密码开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_offline_pd",
        "name": "离线密码解锁上报",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "unlock_phone_remote_kit",
        "name": "手机远程开门记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "lock_alarm_kit",
        "name": "告警记录",
        "type": "Raw",
        "values": "{}"
      },
      {
        "code": "closed_opened_kit",
        "name": "开合状态",
        "type": "Raw",
        "values": "{}"
      }
    ]
  },
  "success": true,
  "t": 1702990556281,
  "tid": "f31b297b9e6d11ee84c63652eadeec7d"
}
uzlonewolf commented 9 months ago

I'm not sure what you mean about the key being encoded. Older keys were hexadecimal strings - they're not encoded, the key just consists of 0-9 and a-f characters. Newer ones use random symbols in addition to the full a-z/A-Z/0-9 character set. I have no idea what IoT API you used so I'm not sure what data it's returning; with the inclusion of symbols it wouldn't surprise me if they're returning it as base64 or something. Edit to add: the final key should be exactly 16 characters/bytes long.

As for using it in tinytuya, you should be able to do it but you're going to need to figure out what data it's expecting for the different DPs. Usually the easiest way to do this is to use the app to perform an action and then look at the device logs in the IoT website to see what it sent. Sometimes you can also open a persistent connection with tinytuya and just watch for async updates sent by the device.

mrbrdo commented 9 months ago

@uzlonewolf thanks. The device doesn't return any DPs though:

DEBUG:decrypted 3.x payload='{"dps":{},"type":"query","t":1702990086}'
DEBUG:payload type = <class 'str'>
DEBUG:decoded results='{"dps":{},"type":"query","t":1702990086}'
DEBUG:status() received data={'dps': {}, 'type': 'query', 't': 1702990086}
DEBUG:Detected dps: {}
result {}

Any idea why that would be?

uzlonewolf commented 9 months ago

Not all devices have persistent status, some only emit events asynchronously. I'd run a monitoring loop (i.e. https://github.com/jasonacox/tinytuya/blob/master/examples/monitor.py ) while entering something on the keypad to see if it sends an event.

I just realized Amazon has these keypads, so I should have one to play around with later today.

mrbrdo commented 9 months ago

That's awesome! I got sick and my keypads are outdoors so I didn't manage to try monitoring yet. I hope it will be possible to access the device :) Adding temporary passwords programatically would be so useful.

uzlonewolf commented 9 months ago

After poking at it a bit, it looks like you can add/change/delete codes locally but not view/list them. You may be able to pull a list from the cloud, I still need to check that. The DP description at https://developer.tuya.com/en/docs/iot/datapoint-reference?id=Kadhd28uy4rvp seems to be pretty accurate.

mrbrdo commented 9 months ago

@uzlonewolf That's awesome, it's enough for my needs (don't need a list). Would you mind sharing your example code if you still have it? Will save me some time as I'm new to tinytuya.

mrbrdo commented 8 months ago

@uzlonewolf do you happen to still have that code?

uzlonewolf commented 8 months ago

I do apologize for the delay, I've been really busy lately. Unfortunately I have only had a chance to look at how they communicate and haven't had a chance to write any code yet. I'll try to get something put together shortly.

mrbrdo commented 8 months ago

Hey @uzlonewolf did you happen to have some time to look at this? Thanks!

uzlonewolf commented 7 months ago

Unfortunately I was not able to get it done before leaving town late last month, but I get back later this week so hopefully I'll have something for you by the weekend.

mrbrdo commented 7 months ago

@uzlonewolf no worries, thanks for your time and effort!

mrbrdo commented 5 months ago

@uzlonewolf did you manage to check it out?