Skip to content

TPAP Implementation#1592

Open
ZeliardM wants to merge 73 commits into
python-kasa:masterfrom
ZeliardM:feature/tpap
Open

TPAP Implementation#1592
ZeliardM wants to merge 73 commits into
python-kasa:masterfrom
ZeliardM:feature/tpap

Conversation

@ZeliardM

Copy link
Copy Markdown
Collaborator

Discovered with new firmware for devices, TP-Link is implementing a new Encryption Type, TPAP. This is an initial implementation to see if the coding works for the handshake. Testing of the code coverage still has to be worked on. The initial implementation includes the new transport, changes to the device_factory to allow devices to select the new transport, and a change to the project to include ecdsa as a new dependency along with cryptography for the new tpaptransport.py.

Copilot AI review requested due to automatic review settings October 18, 2025 19:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Implements the new TPAP (TP-Link Adaptive Protocol) encryption type with SPAKE2+ HTTPS transport for TP-Link devices. This is an initial implementation to test handshake functionality with new firmware that uses TPAP encryption.

  • Added complete TPAP transport implementation using SPAKE2+ P-256 handshake and AEAD data channel
  • Updated device factory to support TPAP encryption type routing
  • Added ecdsa dependency for elliptic curve operations

Reviewed Changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyproject.toml Added ecdsa dependency and mypy overrides for the new package
kasa/transports/tpaptransport.py New TPAP transport implementation with SPAKE2+ handshake and secure channel
kasa/transports/init.py Added TpapTransport to module exports
kasa/deviceconfig.py Added Tpap enum value to DeviceEncryptionType
kasa/device_factory.py Added SMART.TPAP.HTTPS protocol mapping and fixed typo

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread kasa/transports/tpaptransport.py Outdated
Comment thread kasa/transports/tpaptransport.py Outdated
Comment thread kasa/transports/tpaptransport.py Outdated
Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread kasa/transports/tpaptransport.py Fixed
@codecov

codecov Bot commented Oct 18, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.73%. Comparing base (88e1c27) to head (e708447).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1592      +/-   ##
==========================================
+ Coverage   93.21%   93.73%   +0.52%     
==========================================
  Files         157      158       +1     
  Lines        9819    10637     +818     
  Branches     1005     1110     +105     
==========================================
+ Hits         9153     9971     +818     
  Misses        472      472              
  Partials      194      194              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread tests/transports/test_tpaptransport.py Fixed
Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread kasa/transports/tpaptransport.py Fixed
Comment thread tests/transports/test_tpaptransport.py Fixed
@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@rytilahti Ok, I think this is a good first pass. I just need someone with a RV device to test it. The CodeQL Security flags, from my understanding, will always come up with md5 and sha1 hashing in the code, but it's required for the device communication, just like with the other transports.

@danieyal

Copy link
Copy Markdown

Sanitized discovery, logs, and TLS observations for the RV30 Max Plus(EU)-Firmware:1.3.0 Build 250909 Rel.135514 using TPAP. Personally identifying values are redacted. Hope this help.

  • Discovery advertises TPAP with tls=2 and PAKE suite [2]; HTTPS on 4433.
  • TLS1.3 fails immediately; TLS1.2 handshake succeeds with ECDHE-ECDSA-CHACHA20-POLY1305; no ALPN.
  • AES “/app” login on this device returns text/html “200 OK” (likely wrong endpoint for this family).
  • TPAP transport attempts hit TLS alert 40 initially; after constraining to TLS1.2, device still doesn’t accept our HTTP request format (suspect different login paths/headers/body or a pre-HTTP step).

uv run kasa --username '' --password '' --debug --host 192.168.68.63 discover config

DEBUG    Trying to connect with SmartProtocol                                                            discover.py:681
DEBUG    Using SmartDevice for SMART.TAPOROBOVAC                                                   device_factory.py:180
DEBUG    Initializing 192.168.68.63 of type <class 'kasa.smart.smartdevice.SmartDevice'>                   device.py:214
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760838809450,"terminal_uuid":"qHsKLwgAV
         HlHIO3ZttV4Mw==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_i
         nfo"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Posting to https://192.168.68.63:4433/                                                         httpclient.py:88
DEBUG    Device 192.168.68.63 received an os error, enabling sequential request delay: Cannot connect  httpclient.py:136
         to host 192.168.68.63:4433 ssl:<ssl.SSLContext object at 0x000001B6D6A4F7D0> [[SSL:
         SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)]
DEBUG    Device 192.168.68.63 got a connection error, will retry 3 times: ('Device connection       smartprotocol.py:145
         error: 192.168.68.63: Cannot connect to host 192.168.68.63:4433 ssl:<ssl.SSLContext object
         at 0x000001B6D6A4F7D0> [[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure
         (_ssl.c:1000)]', ClientConnectorSSLError(ConnectionKey(host='192.168.68.63', port=4433,
         is_ssl=True, ssl=<ssl.SSLContext object at 0x000001B6D6A4F7D0>, proxy=None,
         proxy_auth=None, proxy_headers_hash=None), SSLError(1, '[SSL:
         SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)')))
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760838809471,"terminal_uuid":"qHsKLwgAV
         HlHIO3ZttV4Mw==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_i
         nfo"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Device 192.168.68.63 waiting 0.25 seconds to send request                                      httpclient.py:81
DEBUG    Posting to https://192.168.68.63:4433/                                                         httpclient.py:88
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760838809754,"terminal_uuid":"qHsKLwgAV
         HlHIO3ZttV4Mw==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_i
         nfo"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Device 192.168.68.63 waiting 0.25 seconds to send request                                      httpclient.py:81
DEBUG    Posting to https://192.168.68.63:4433/                                                         httpclient.py:88
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760838810228,"terminal_uuid":"qHsKLwgAV
         HlHIO3ZttV4Mw==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_i
         nfo"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Device 192.168.68.63 waiting 0.25 seconds to send request                                      httpclient.py:81
DEBUG    Posting to https://192.168.68.63:4433/                                                         httpclient.py:88
DEBUG    Giving up on 192.168.68.63 after 3 retries                                                 smartprotocol.py:152
DEBUG    Unable to connect with SmartProtocol: ('Device connection error: 192.168.68.63: Cannot connect  discover.py:684
         to host 192.168.68.63:4433 ssl:<ssl.SSLContext object at 0x000001B6D6A4F7D0> [[SSL:
         SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)]',
         ClientConnectorSSLError(ConnectionKey(host='192.168.68.63', port=4433, is_ssl=True,
         ssl=<ssl.SSLContext object at 0x000001B6D6A4F7D0>, proxy=None, proxy_auth=None,
         proxy_headers_hash=None), SSLError(1, '[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert
         handshake failure (_ssl.c:1000)')))
Attempt to connect to 192.168.68.63 with SmartProtocol + TpapTransport + SmartDevice + https failed
Unable to connect to 192.168.68.63

uv run kasa --username '' --password '' --debug --host 192.168.68.63 discover raw

Discovering device 192.168.68.63 for 10 seconds
DEBUG    [DISCOVERY] 192.168.68.63 >> {'system': {'get_sysinfo': {}}}                                    discover.py:326
DEBUG    Waiting a total of 10 seconds for responses...                                                  discover.py:587
{
  "discovery_response": {
    "result": {
      "device_id": "REDACTED_f47bb068b8aacda06dec54e",
      "owner": "REDACTED_96B05E0B002C122F61EC032",
      "device_type": "SMART.TAPOROBOVAC",
      "device_model": "RV30 Max Plus(EU)",
      "ip": "192.168.68.63",
      "mac": "BC-07-1D-00-00-00",
      "is_support_iot_cloud": true,
      "tpap": {
        "tls": 2,
        "dac": 1,
        "noc": 1,
        "pake": [
          2
        ],
        "port": 4433
      },
      "obd_src": "tplink",
      "protocol_version": 1,
      "factory_default": false,
      "mgt_encrypt_schm": {
        "is_support_https": true,
        "http_port": 4433,
        "encrypt_type": "TPAP",
        "lv": 2
      }
    },
    "error_code": 0
  },
  "meta": {
    "ip": "192.168.68.63",
    "port": 20002
  }
}
DEBUG    Using SmartDevice for SMART.TAPOROBOVAC                                                   device_factory.py:180
DEBUG    Finding protocol for 192.168.68.63                                                        device_factory.py:195
DEBUG    Finding protocol for DeviceFamily.SmartTapoRobovac                                        device_factory.py:198
DEBUG    Finding transport for SMART.TPAP.HTTPS                                                    device_factory.py:227
DEBUG    [DISCOVERY] 192.168.68.63 << {'error_code': 0,                                                  discover.py:924
          'result': {'device_id': 'REDACTED_f47bb068b8aacda06dec54e',
                     'device_model': 'RV30 Max Plus(EU)',
                     'device_type': 'SMART.TAPOROBOVAC',
                     'factory_default': False,
                     'ip': '192.168.68.63',
                     'is_support_iot_cloud': True,
                     'mac': 'BC-07-1D-00-00-00',
                     'mgt_encrypt_schm': {'encrypt_type': 'TPAP',
                                          'http_port': 4433,
                                          'is_support_https': True,
                                          'lv': 2},
                     'obd_src': 'tplink',
                     'owner': 'REDACTED_96B05E0B002C122F61EC032',
                     'protocol_version': 1,
                     'tpap': {'dac': 1, 'noc': 1, 'pake': [2], 'port': 4433, 'tls': 2}}}
DEBUG    Initializing 192.168.68.63 of type <class 'kasa.smart.smartdevice.SmartDevice'>                   device.py:214

SSL:

root@pve:~# openssl s_client -connect 192.168.68.63:4433 -tls1_3 -alpn h2
Connecting to 192.168.68.63
CONNECTED(00000003)
40D767A1D0750000:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:../ssl/record/rec_layer_s3.c:916:SSL alert number 40
---
no peer certificate available
---
No client certificate CA names sent
Negotiated TLS1.3 group: <NULL>
---
SSL handshake has read 7 bytes and written 1469 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Protocol: TLSv1.3
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
root@pve:~# openssl s_client -connect 192.168.68.63:4433 -tls1_2 -alpn h2
Connecting to 192.168.68.63
CONNECTED(00000003)
Can't use SSL_get_servername
depth=1 CN=SMART.TAPOROBOVAC CA, O=TP-LINK SYSTEMS INC., L=Irvine, ST=California, C=US
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN=RV30 Max Plus(EU), OU=SMART.TAPOHUB
verify return:1
---
Certificate chain
 0 s:CN=RV30 Max Plus(EU), OU=SMART.TAPOHUB
   i:CN=SMART.TAPOROBOVAC CA, O=TP-LINK SYSTEMS INC., L=Irvine, ST=California, C=US
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Sep 19 20:43:33 2025 GMT; NotAfter: Sep 19 20:43:33 2030 GMT
 1 s:CN=SMART.TAPOROBOVAC CA, O=TP-LINK SYSTEMS INC., L=Irvine, ST=California, C=US
   i:CN=TP-LINK SYSTEMS DEVICE ROOT CA, O=TP-LINK SYSTEMS INC., L=Irvine, ST=California, C=US
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Nov 22 04:07:54 2024 GMT; NotAfter: Nov 20 04:07:54 2034 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
<REDACTED>
-----END CERTIFICATE-----
subject=CN=RV30 Max Plus(EU), OU=SMART.TAPOHUB
issuer=CN=SMART.TAPOROBOVAC CA, O=TP-LINK SYSTEMS INC., L=Irvine, ST=California, C=US
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ecdsa_secp256r1_sha256
Peer Temp Key: ECDH, secp384r1, 384 bits
---
SSL handshake has read 1476 bytes and written 350 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.2, Cipher is ECDHE-ECDSA-CHACHA20-POLY1305
Protocol: TLSv1.2
Server public key is 256 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-ECDSA-CHACHA20-POLY1305
    Session-ID: <REDACTED>
    Session-ID-ctx:
    Master-Key: <REDACTED>
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1760847175
    Timeout   : 7200 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
    Extended master secret: yes
---

Wireshark:

Frame 221: Packet, 234 bytes on wire (1872 bits), 234 bytes captured (1872 bits) on interface \Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A}, id 0
    Section number: 1
    Interface id: 0 (\Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A})
        Interface name: \Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A}
        Interface description: Ethernet
    Encapsulation type: Ethernet (1)
    Arrival Time: Oct 19, 2025 11:35:25.271976000 Malay Peninsula Standard Time
    UTC Arrival Time: Oct 19, 2025 03:35:25.271976000 UTC
    Epoch Arrival Time: 1760844925.271976000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 1.073000 milliseconds]
    [Time delta from previous displayed frame: 1.073000 milliseconds]
    [Time since reference or first frame: 3 minutes, 2.003639000 seconds]
    Frame Number: 221
    Frame Length: 234 bytes (1872 bits)
    Capture Length: 234 bytes (1872 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ip:tcp:tls]
    Character encoding: ASCII (0)
    [Coloring Rule Name: TCP]
    [Coloring Rule String: tcp]
Ethernet II, Src: CompalInform_02:ed:ec (38:a7:46:02:ed:ec), Dst: TPLink_77:0e:7d (bc:07:1d:77:0e:7d)
    Destination: TPLink_77:0e:7d (bc:07:1d:77:0e:7d)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Source: CompalInform_02:ed:ec (38:a7:46:02:ed:ec)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IPv4 (0x0800)
    [Stream index: 0]
Internet Protocol Version 4, Src: 192.168.68.91, Dst: 192.168.68.63
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes (5)
    Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
        0000 00.. = Differentiated Services Codepoint: Default (0)
        .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0)
    Total Length: 220
    Identification: 0x2625 (9765)
    010. .... = Flags: 0x2, Don't fragment
        0... .... = Reserved bit: Not set
        .1.. .... = Don't fragment: Set
        ..0. .... = More fragments: Not set
    ...0 0000 0000 0000 = Fragment Offset: 0
    Time to Live: 128
    Protocol: TCP (6)
    Header Checksum: 0x0000 [validation disabled]
    [Header checksum status: Unverified]
    Source Address: 192.168.68.91
    Destination Address: 192.168.68.63
    [Stream index: 0]
Transmission Control Protocol, Src Port: 46387, Dst Port: 4433, Seq: 1, Ack: 1, Len: 180
    Source Port: 46387
    Destination Port: 4433
    [Stream index: 5]
    [Stream Packet Number: 4]
    [Conversation completeness: Incomplete, DATA (15)]
        ..0. .... = RST: Absent
        ...0 .... = FIN: Absent
        .... 1... = Data: Present
        .... .1.. = ACK: Present
        .... ..1. = SYN-ACK: Present
        .... ...1 = SYN: Present
        [Completeness Flags: ··DASS]
    [TCP Segment Len: 180]
    Sequence Number: 1    (relative sequence number)
    Sequence Number (raw): 2124410112
    [Next Sequence Number: 181    (relative sequence number)]
    Acknowledgment Number: 1    (relative ack number)
    Acknowledgment number (raw): 1100174539
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x018 (PSH, ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Accurate ECN: Not set
        .... 0... .... = Congestion Window Reduced: Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set
        .... .... 1... = Push: Set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······AP···]
    Window: 255
    [Calculated window size: 65280]
    [Window size scaling factor: 256]
    Checksum: 0x0aba [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    [Timestamps]
        [Time since first frame in this TCP stream: 8.459000 milliseconds]
        [Time since previous frame in this TCP stream: 1.073000 milliseconds]
    [SEQ/ACK analysis]
        [iRTT: 7.386000 milliseconds]
        [Bytes in flight: 180]
        [Bytes sent since last PSH flag: 180]
    [Client Contiguous Streams: 1]
    [Server Contiguous Streams: 1]
    TCP payload (180 bytes)
Transport Layer Security
    [Stream index: 2]
    TLSv1.2 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 175
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 171
            Version: TLS 1.2 (0x0303)
            Random: 3cbe0eae6e3be4e450edfdf4d611302985ecb513dc5d147633b7e16465777bf3
                GMT Unix Time: Apr 18, 2002 08:09:18.000000000 Malay Peninsula Standard Time
                Random Bytes: 6e3be4e450edfdf4d611302985ecb513dc5d147633b7e16465777bf3
            Session ID Length: 32
            Session ID: 1b04dc59c6d238c56aff3701dc937ef2e4a14eed2c195aa26d2a880c4b9c8af1
            Cipher Suites Length: 16
            Cipher Suites (8 suites)
                Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
                Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
                Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
                Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
                Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
                Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
                Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
                Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
            Compression Methods Length: 1
            Compression Methods (1 method)
                Compression Method: null (0)
            Extensions Length: 82
            Extension: renegotiation_info (len=1)
                Type: renegotiation_info (65281)
                Length: 1
                Renegotiation Info extension
                    Renegotiation info extension length: 0
            Extension: extended_master_secret (len=0)
                Type: extended_master_secret (23)
                Length: 0
            Extension: session_ticket (len=0)
                Type: session_ticket (35)
                Length: 0
                Session Ticket: <MISSING>
            Extension: signature_algorithms (len=20)
                Type: signature_algorithms (13)
                Length: 20
                Signature Hash Algorithms Length: 18
                Signature Hash Algorithms (9 algorithms)
                    Signature Algorithm: ecdsa_secp256r1_sha256 (0x0403)
                        Signature Hash Algorithm Hash: SHA256 (4)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: rsa_pss_rsae_sha256 (0x0804)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (4)
                    Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
                        Signature Hash Algorithm Hash: SHA256 (4)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: ecdsa_secp384r1_sha384 (0x0503)
                        Signature Hash Algorithm Hash: SHA384 (5)
                        Signature Hash Algorithm Signature: ECDSA (3)
                    Signature Algorithm: rsa_pss_rsae_sha384 (0x0805)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (5)
                    Signature Algorithm: rsa_pkcs1_sha384 (0x0501)
                        Signature Hash Algorithm Hash: SHA384 (5)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: rsa_pss_rsae_sha512 (0x0806)
                        Signature Hash Algorithm Hash: Unknown (8)
                        Signature Hash Algorithm Signature: Unknown (6)
                    Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
                        Signature Hash Algorithm Hash: SHA512 (6)
                        Signature Hash Algorithm Signature: RSA (1)
                    Signature Algorithm: rsa_pkcs1_sha1 (0x0201)
                        Signature Hash Algorithm Hash: SHA1 (2)
                        Signature Hash Algorithm Signature: RSA (1)
            Extension: status_request (len=5)
                Type: status_request (5)
                Length: 5
                Certificate Status Type: OCSP (1)
                Responder ID list Length: 0
                Request Extensions Length: 0
            Extension: application_layer_protocol_negotiation (len=14)
                Type: application_layer_protocol_negotiation (16)
                Length: 14
                ALPN Extension Length: 12
                ALPN Protocol
                    ALPN string length: 2
                    ALPN Next Protocol: h2
                    ALPN string length: 8
                    ALPN Next Protocol: http/1.1
            Extension: ec_point_formats (len=2)
                Type: ec_point_formats (11)
                Length: 2
                EC point formats Length: 1
                Elliptic curves point formats (1)
                    EC point format: uncompressed (0)
            Extension: supported_groups (len=8)
                Type: supported_groups (10)
                Length: 8
                Supported Groups List Length: 6
                Supported Groups (3 groups)
                    Supported Group: x25519 (0x001d)
                    Supported Group: secp256r1 (0x0017)
                    Supported Group: secp384r1 (0x0018)

Frame 224: Packet, 63 bytes on wire (504 bits), 63 bytes captured (504 bits) on interface \Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A}, id 0
    Section number: 1
    Interface id: 0 (\Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A})
        Interface name: \Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A}
        Interface description: Ethernet
    Encapsulation type: Ethernet (1)
    Arrival Time: Oct 19, 2025 11:35:25.281849000 Malay Peninsula Standard Time
    UTC Arrival Time: Oct 19, 2025 03:35:25.281849000 UTC
    Epoch Arrival Time: 1760844925.281849000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 4.004000 milliseconds]
    [Time delta from previous displayed frame: 4.004000 milliseconds]
    [Time since reference or first frame: 3 minutes, 2.013512000 seconds]
    Frame Number: 224
    Frame Length: 63 bytes (504 bits)
    Capture Length: 63 bytes (504 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ip:tcp:tls]
    Character encoding: ASCII (0)
    [Coloring Rule Name: TCP]
    [Coloring Rule String: tcp]
Ethernet II, Src: TPLink_77:0e:7d (bc:07:1d:77:0e:7d), Dst: CompalInform_02:ed:ec (38:a7:46:02:ed:ec)
    Destination: CompalInform_02:ed:ec (38:a7:46:02:ed:ec)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Source: TPLink_77:0e:7d (bc:07:1d:77:0e:7d)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IPv4 (0x0800)
    [Stream index: 0]
Internet Protocol Version 4, Src: 192.168.68.63, Dst: 192.168.68.91
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes (5)
    Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
        0000 00.. = Differentiated Services Codepoint: Default (0)
        .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0)
    Total Length: 49
    Identification: 0x2213 (8723)
    010. .... = Flags: 0x2, Don't fragment
        0... .... = Reserved bit: Not set
        .1.. .... = Don't fragment: Set
        ..0. .... = More fragments: Not set
    ...0 0000 0000 0000 = Fragment Offset: 0
    Time to Live: 64
    Protocol: TCP (6)
    Header Checksum: 0x0ec9 [validation disabled]
    [Header checksum status: Unverified]
    Source Address: 192.168.68.63
    Destination Address: 192.168.68.91
    [Stream index: 0]
Transmission Control Protocol, Src Port: 4433, Dst Port: 46386, Seq: 1426, Ack: 181, Len: 9
    Source Port: 4433
    Destination Port: 46386
    [Stream index: 4]
    [Stream Packet Number: 12]
    [Conversation completeness: Incomplete, DATA (15)]
        ..0. .... = RST: Absent
        ...0 .... = FIN: Absent
        .... 1... = Data: Present
        .... .1.. = ACK: Present
        .... ..1. = SYN-ACK: Present
        .... ...1 = SYN: Present
        [Completeness Flags: ··DASS]
    [TCP Segment Len: 9]
    Sequence Number: 1426    (relative sequence number)
    Sequence Number (raw): 1085180748
    [Next Sequence Number: 1435    (relative sequence number)]
    Acknowledgment Number: 181    (relative ack number)
    Acknowledgment number (raw): 241256951
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x018 (PSH, ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Accurate ECN: Not set
        .... 0... .... = Congestion Window Reduced: Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set
        .... .... 1... = Push: Set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······AP···]
    Window: 1892
    [Calculated window size: 30272]
    [Window size scaling factor: 16]
    Checksum: 0x968c [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    [Timestamps]
        [Time since first frame in this TCP stream: 216.772000 milliseconds]
        [Time since previous frame in this TCP stream: 4.204000 milliseconds]
    [SEQ/ACK analysis]
        [iRTT: 11.499000 milliseconds]
        [Bytes in flight: 9]
        [Bytes sent since last PSH flag: 9]
    [Client Contiguous Streams: 1]
    [Server Contiguous Streams: 1]
    TCP payload (9 bytes)
Transport Layer Security
    [Stream index: 1]
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 4
        Handshake Protocol: Server Hello Done
            Handshake Type: Server Hello Done (14)
            Length: 0
Frame 225: Packet, 212 bytes on wire (1696 bits), 212 bytes captured (1696 bits) on interface \Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A}, id 0
    Section number: 1
    Interface id: 0 (\Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A})
        Interface name: \Device\NPF_{5B7CFEBB-96D4-4EA5-A9EB-AEB90614BE7A}
        Interface description: Ethernet
    Encapsulation type: Ethernet (1)
    Arrival Time: Oct 19, 2025 11:35:25.284112000 Malay Peninsula Standard Time
    UTC Arrival Time: Oct 19, 2025 03:35:25.284112000 UTC
    Epoch Arrival Time: 1760844925.284112000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 2.263000 milliseconds]
    [Time delta from previous displayed frame: 2.263000 milliseconds]
    [Time since reference or first frame: 3 minutes, 2.015775000 seconds]
    Frame Number: 225
    Frame Length: 212 bytes (1696 bits)
    Capture Length: 212 bytes (1696 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ip:tcp:tls]
    Character encoding: ASCII (0)
    [Coloring Rule Name: TCP]
    [Coloring Rule String: tcp]
Ethernet II, Src: CompalInform_02:ed:ec (38:a7:46:02:ed:ec), Dst: TPLink_77:0e:7d (bc:07:1d:77:0e:7d)
    Destination: TPLink_77:0e:7d (bc:07:1d:77:0e:7d)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Source: CompalInform_02:ed:ec (38:a7:46:02:ed:ec)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IPv4 (0x0800)
    [Stream index: 0]
Internet Protocol Version 4, Src: 192.168.68.91, Dst: 192.168.68.63
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes (5)
    Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
        0000 00.. = Differentiated Services Codepoint: Default (0)
        .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0)
    Total Length: 198
    Identification: 0x2627 (9767)
    010. .... = Flags: 0x2, Don't fragment
        0... .... = Reserved bit: Not set
        .1.. .... = Don't fragment: Set
        ..0. .... = More fragments: Not set
    ...0 0000 0000 0000 = Fragment Offset: 0
    Time to Live: 128
    Protocol: TCP (6)
    Header Checksum: 0x0000 [validation disabled]
    [Header checksum status: Unverified]
    Source Address: 192.168.68.91
    Destination Address: 192.168.68.63
    [Stream index: 0]
Transmission Control Protocol, Src Port: 46386, Dst Port: 4433, Seq: 181, Ack: 1435, Len: 158
    Source Port: 46386
    Destination Port: 4433
    [Stream index: 4]
    [Stream Packet Number: 13]
    [Conversation completeness: Incomplete, DATA (15)]
        ..0. .... = RST: Absent
        ...0 .... = FIN: Absent
        .... 1... = Data: Present
        .... .1.. = ACK: Present
        .... ..1. = SYN-ACK: Present
        .... ...1 = SYN: Present
        [Completeness Flags: ··DASS]
    [TCP Segment Len: 158]
    Sequence Number: 181    (relative sequence number)
    Sequence Number (raw): 241256951
    [Next Sequence Number: 339    (relative sequence number)]
    Acknowledgment Number: 1435    (relative ack number)
    Acknowledgment number (raw): 1085180757
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x018 (PSH, ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Accurate ECN: Not set
        .... 0... .... = Congestion Window Reduced: Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set
        .... .... 1... = Push: Set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······AP···]
    Window: 250
    [Calculated window size: 64000]
    [Window size scaling factor: 256]
    Checksum: 0x0aa4 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    [Timestamps]
        [Time since first frame in this TCP stream: 219.035000 milliseconds]
        [Time since previous frame in this TCP stream: 2.263000 milliseconds]
    [SEQ/ACK analysis]
        [This is an ACK to the segment in frame: 224]
        [The RTT to ACK the segment was: 2.263000 milliseconds]
        [iRTT: 11.499000 milliseconds]
        [Bytes in flight: 158]
        [Bytes sent since last PSH flag: 158]
    [Client Contiguous Streams: 1]
    [Server Contiguous Streams: 1]
    TCP payload (158 bytes)
Transport Layer Security
    [Stream index: 1]
    TLSv1.2 Record Layer: Handshake Protocol: Client Key Exchange
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 102
        Handshake Protocol: Client Key Exchange
            Handshake Type: Client Key Exchange (16)
            Length: 98
            EC Diffie-Hellman Client Params
                Pubkey Length: 97
                Pubkey: 0495574c7aa82261d993d4498805cc1090590eb703e0f85f77a68b4ca4f6dc148b8d401ff25e0359f99a06473e8fa771956ecbcf6512de82961734bc2584546b0ffc4ebcd2d787aa39402881fb51297e8dab3dc0f887e863494fa8b8fba336c0c6
    TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
        Content Type: Change Cipher Spec (20)
        Version: TLS 1.2 (0x0303)
        Length: 1
        Change Cipher Spec Message
    TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 40
        Handshake Protocol: Encrypted Handshake Message

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal Pull the latest commit and try giving it a shot again.

@danieyal

Copy link
Copy Markdown

unfortunately, still the same error.

DEBUG    Trying to connect with SmartProtocol                                                             discover.py:681
DEBUG    Using SmartDevice for SMART.TAPOROBOVAC                                                    device_factory.py:180
DEBUG    Initializing 192.168.68.63 of type <class 'kasa.smart.smartdevice.SmartDevice'>                    device.py:214
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                 smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760887822371,"terminal_uuid":"mRY0IrNONh
         G5kZRhIPcYrg==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_inf
         o"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Posting to https://192.168.68.63:4433/                                                          httpclient.py:88
DEBUG    Device 192.168.68.63 received an os error, enabling sequential request delay: Cannot connect   httpclient.py:157
         to host 192.168.68.63:4433 ssl:<ssl.SSLContext object at 0x0000025A3D7D7550> [[SSL:
         SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)]
DEBUG    Device 192.168.68.63 got a connection error, will retry 3 times: ('Device connection error: smartprotocol.py:145
         192.168.68.63: Cannot connect to host 192.168.68.63:4433 ssl:<ssl.SSLContext object at
         0x0000025A3D7D7550> [[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure
         (_ssl.c:1000)]', ClientConnectorSSLError(ConnectionKey(host='192.168.68.63', port=4433,
         is_ssl=True, ssl=<ssl.SSLContext object at 0x0000025A3D7D7550>, proxy=None,
         proxy_auth=None, proxy_headers_hash=None), SSLError(1, '[SSL:
         SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)')))
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                 smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760887822496,"terminal_uuid":"mRY0IrNONh
         G5kZRhIPcYrg==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_inf
         o"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Device 192.168.68.63 waiting 0.25 seconds to send request                                       httpclient.py:81
DEBUG    Posting to https://192.168.68.63:4433/                                                          httpclient.py:88
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                 smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760887823014,"terminal_uuid":"mRY0IrNONh
         G5kZRhIPcYrg==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_inf
         o"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Device 192.168.68.63 waiting 0.25 seconds to send request                                       httpclient.py:81
DEBUG    Posting to https://192.168.68.63:4433/                                                          httpclient.py:88
DEBUG    192.168.68.63 multi-request-batch-1-of-1 >>                                                 smartprotocol.py:241
         '{"method":"multipleRequest","request_time_milis":1760887823298,"terminal_uuid":"mRY0IrNONh
         G5kZRhIPcYrg==","params":{"requests":[{"method":"component_nego"},{"method":"get_device_inf
         o"},{"method":"get_connect_cloud_state"}]}}'
DEBUG    Device 192.168.68.63 waiting 0.25 seconds to send request                                       httpclient.py:81
DEBUG    Posting to https://192.168.68.63:4433/                                                          httpclient.py:88
DEBUG    Giving up on 192.168.68.63 after 3 retries                                                  smartprotocol.py:152
DEBUG    Unable to connect with SmartProtocol: ('Device connection error: 192.168.68.63: Cannot connect   discover.py:684
         to host 192.168.68.63:4433 ssl:<ssl.SSLContext object at 0x0000025A3D7D7550> [[SSL:
         SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)]',
         ClientConnectorSSLError(ConnectionKey(host='192.168.68.63', port=4433, is_ssl=True,
         ssl=<ssl.SSLContext object at 0x0000025A3D7D7550>, proxy=None, proxy_auth=None,
         proxy_headers_hash=None), SSLError(1, '[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert
         handshake failure (_ssl.c:1000)')))
Attempt to connect to 192.168.68.63 with SmartProtocol + TpapTransport + SmartDevice + https failed
Unable to connect to 192.168.68.63

@danieyal

Copy link
Copy Markdown

I think i have got the TLS working now but stuck on the authentication now.

  • TLS 1.2, cipher ECDHE-ECDSA-AES-256-GCM-SHA384
  • No SNI (IP direct)
  • ALPN offered: h2, http/1.1
  • Server certificate chain: TP-LINK SYSTEMS DEVICE ROOT CA → SMART.TAPOROBOVAC CA → device leaf (CN RV30 Max Plus(EU), OU SMART.TAPOHUB)
  • OCSP AIA: http://ocsp-tss.tplinkcloud.com
  • No CertificateRequest observed → not mTLS
CIPHERS = ":".join(
        [
            "ECDHE-ECDSA-AES256-GCM-SHA384",
            "ECDHE-ECDSA-CHACHA20-POLY1305",
            "ECDHE-ECDSA-AES128-GCM-SHA256",
            "AES256-GCM-SHA384",
            "AES256-SHA256",
            "AES128-GCM-SHA256",
            "AES128-SHA256",
            "AES256-SHA",
        ]
    )

The device is returning a JSON response with 'error_code': -2402 along with authentication failure details like failedAttempts and remainAttempts. The device is rejecting the authentication attempt at the pake_register stage returning error code -2402. My theory is the device is actively rejecting our credentials/authentication attempt before we even get to the SPAKE2+ cryptographic exchange. This suggests the device needs something we're not providing, most likely DAC support.

DEBUG    TPAP register(empty) response: status=200, data_type=dict, data_len=3,
         data={'error_info': {'lockedMinute': 0, 'failedAttempts': 4, 'remainAttempts': 11},
         'result': {'sub_method': 'pake_register'}, 'error_code': -2402}

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

I have been working on this today without much movement, still trying to figure out the authentication pieces. Looks like I will have to implement NOC but having issues getting the information and URLs for the certificate registration with the Tapo cloud. There is an API rate limit which causes problems as well. So, I'm still working on this, but nothing yet.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal Are you able to use the Tapo app on your computer with Wireshark? I'm trying to reverse engineer the url for the certificates and the requests are not working. I am looking for something to do with:
nbu.cvm-server-v2

This is what points to where to apply for the certificates, but I can't get the communication to work correctly on my end. I'm trying to get the signature correct with the app to the cloud so I can pull the url, but I can't get that either. Until I have the URL to work with the serviceId: nbu.cvm-server-v2, then I can't get the library to handle the noc certificates.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal try pulling again and let me know. I updated the ciphers like you found and I also corrected the last error you posted, it went to the stok field in the register parameters, it needed to be sessionId instead. Let me know what you get with this.

@danieyal

danieyal commented Oct 22, 2025

Copy link
Copy Markdown

@ZeliardM Thanks for the updates, and to clarify my side:

  • I can’t use Wireshark alone due to TLS/Cert pinning; I’m using Frida. I’ve posted sanitized findings (TLS, DS wrapper, discovery, logs) in my report and can share more privately if needed.
  • RV30 appears to commission via cloud and only then uses local DS:
    • Local DS: POST https://<ip>:4433/stok=<REDACTED>/ds with headers Accept: application/octet-stream, Connection: keep-alive, body wrapper:
      {"inputParams":{"requestData":{<SMART JSON>}},"serviceId":"passthrough"}
    • Plain JSON to /ds returns {"error_code": -2402} on this firmware, implying DS expects the SPAKE-protected binary frames tied to the cloud-issued session.
  • TLS (local): TLS 1.2 only, ECDHE‑ECDSA (AES‑256‑GCM/CHACHA20), no SNI, ALPN offered h2/http/1.1, device ECDSA leaf via SMART.TAPOROBOVAC CA.

Re: NOC/nbu.cvm-server-v2:

  • I haven’t seen any local pake_register at all; the app drives commissioning over cloud (…/v1/things/<thing-id>/services-sync) and then hits local DS.
  • I’ll keep trying to capture the exact services-sync request/response for serviceId: nbu.cvm-server-v2 with Frida (and sanitize), but so far, I have not seen it at all. If someone already has the signed payload/URL shape, that would unblock wiring NOC into the library much faster.
  • I cannot directly see DAC/NOC (Matter) details locally since those are invoked against the cloud endpoints.

I also pulled the latest branch:

  • Cipher updates look good.
  • Current failure is still -2402 at register.

Frida Log for device commissioning, I reset my device to see if pake_register appear at all, but it seems like nope, it just communicates over cloud regardless, other Tapo devices (plug, bulb, hub, camera) that I own still communicates locally (so probably not local network issue), I can 'see' the local endpoint for other devices but only vacuum just straight to cloud endpoint. Sorry, if it takes too long, setting up Frida took longer than expected.

frida -U -f com.tplink.iot -l .\unpin.js -l .\okhttp_log.js -l .\mac_sniff.js -l .\json_watch.js -l .\native_sock_hook.js -l .\hook_socket_write.js -l .\hook_socket_java.js -l .\okhttp_resp_tap.js
     ____
    / _  |   Frida 17.4.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
Waiting for USB device to appear...
   . . . .
   . . . .   Connected to Redmi Note 5 (id=adb-21f3ca07-4K2z0L._adb-tls-connect._tcp)
Spawned `com.tplink.iot`. Resuming main thread!

---- OKHTTP REQUEST ----
URL: https://n-aps1-wap.i.tplinkcloud.com/api/v2/account/checkPassword
Method: POST
Headers:
signature-required: true
token-required: false

Body:
{"appType":"TP-Link_Tapo_Android","cloudPassword":"<REDACTED>","cloudUserName":"<REDACTED>"}
------------------------
[KeySpec] algo=HmacSHA1 key=54,101,100,55,100,57,55,102,51,101,55,51,52,54,55,......
[Mac] getInstance: HmacSHA1
---- OKHTTP REQUEST ----
URL: https://nbu.iot-app-server.app-v2/v1/families/thing-onboarding
Method: POST
Headers:

Body:
{"familyId":"default","roomId":"2sVeb2pm","thingNames":["<REDACTED>"]}
------------------------
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://192.168.68.63:4433/stok=<REDACTED>/ds
Method: POST
Headers:
Connection: keep-alive
Accept: application/octet-stream

Body:
  fDɣ   S5*:  3  @ 2 ԧ  k JٗhC W x~  I   p 
------------------------
[Socket.connect] addr=/192.168.68.63:4433 timeout=30000
---- OKHTTP REQUEST ----
URL: https://n-aps1-wap.i.tplinkcloud.com/api/v2/common/getDeviceListByPage
Method: POST
Headers:
signature-required: true

Body:
{"deviceTypeList":["SMART.TAPOPLUG","SMART.TAPOBULB","SMART.IPCAMERA","SMART.TAPOHUB","SMART.TAPOSENSOR","SMART.TAPOSWITCH","SMART.TAPOROBOVAC","SMART.TAPODOORBELL","SMART.TAPOLOCK","SMART.TAPOREMOTE","SMART.TAPOCHIME","SMART.KASAPLUG","SMART.KASASWITCH","SMART.KASAHUB","SMART.KASAENERGY","IOT.IPCAMERA"],"index":0,"limit":20}
------------------------
[KeySpec] algo=HmacSHA1 key=54,101,100,55,100,57,55,102,51,101,55,51,52,54,55,......
[Mac] getInstance: HmacSHA1
[Socket.connect] addr=n-aps1-wap.i.tplinkcloud.com/47.130.43.98:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://nbu.iot-app-server.app-v2/v2/things?page=0&pageSize=100&deviceTypes=SMART.TAPOPLUG%2CSMART.TAPOBULB%2CSMART.IPCAMERA%2CSMART.TAPOHUB%2CSMART.TAPOSENSOR%2CSMART.TAPOSWITCH%2CSMART.TAPOROBOVAC%2CSMART.TAPODOORBELL%2CSMART.TAPOLOCK%2CSMART.TAPOREMOTE%2CSMART.TAPOCHIME%2CIOT.SMARTPLUGSWITCH%2CIOT.SMARTBULB%2CIOT.IPCAMERA%2CIOT.HUB%2CIOT.RANGEEXTENDER%2CIOT.RANGEEXTENDER.SMARTPLUG%2CIOT.ROUTER%2CSMART.KASAPLUG%2CSMART.KASASWITCH%2CSMART.KASAHUB%2CSMART.KASAENERGY%2CSMART.MATTERPLUG%2CSMART.MATTERBULB&includePcDevice=false&includeKasaShareDevices=true&includeMatterDevice=false
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/rt-info
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://192.168.68.63:4433/stok=<REDACTED>/ds
Method: POST
Headers:
Connection: keep-alive
Accept: application/octet-stream

Body:
  g  6B
  Y >   Q!Tf ^  FsD  NHL
------------------------
[Socket.connect] addr=/192.168.68.63:4433 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/details
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/settings
Method: GET
Headers:

logReq err: TypeError: not a function
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/components
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://n-aps1-wap.i.tplinkcloud.com/api/v2/common/getIntlFwList
Method: POST
Headers:
signature-required: true

Body:
{"devFwCurrentVer":"1.3.0","deviceId":"<REDACTED>","fwId":"00000000000000000000000000000000","hwId":"FE727A169352FE69<REDACTED>","locale":"en_US","oemId":"E24B0568952B79<REDACTED>"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"setQuickSetupExtraInfo","params":{"nickname":"Um9ib3QgVmFjdXVt"}}]}}},"serviceId":"passthrough"}
------------------------
[KeySpec] algo=HmacSHA1 key=54,101,100,55,100,57,55,102,51,101,55,51,52,54,55,......
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"account_sync","params":{"cloud_account":{"password":"<REDACTED>","username":"<REDACTED>,"serviceId":"passthrough"}
------------------------
[Mac] getInstance: HmacSHA1
[Socket.connect] addr=n-aps1-wap.i.tplinkcloud.com/47.130.43.98:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/components
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/voice-packages?deviceModel=RV30%20Max%20Plus
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://192.168.68.63:4433/stok=<REDACTED>/ds
Method: POST
Headers:
Connection: keep-alive
Accept: application/octet-stream

Body:
      _Tt3 v LB Q[iw LaG    @ T 24 ؏ 
------------------------
[Socket.connect] addr=/192.168.68.63:4433 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/components
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/features/autoUpdateMode
Method: PATCH
Headers:

Body:
{"enable":true,"random_range":120,"time":180}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCurrentVoiceLanguage"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/voice-packages?deviceModel=RV30
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://192.168.68.63:4433/stok=<REDACTED>/ds
Method: POST
Headers:
Connection: keep-alive
Accept: application/octet-stream

Body:
  Ô{ F ZR  =F    |ZK0K  @   A  8    v;|Z  e 
------------------------
[Socket.connect] addr=/192.168.68.63:4433 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/components
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/voice-packages/signed-url?voicePackageId=2
Method: GET
Headers:

logReq err: TypeError: not a function
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"setVoiceLanguage","params":{"md5":"055ba03d9973ac5dd","name":"bb053ca2c56f3902b","url":"https://prd-iot-as-aps1.s3.ap-southeast-1.amazonaws.com/%2FvoicePackages/voicePackages2/2_English_Female_American.tar.gz?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEHYaDmFwLXNvdXRoZWFzdC0xIkgwRgIhAM3vt3Fd2jRBA8aYB71rzzu1Woohe1O%2BvnvOlH4wLz3CAiEAzvdnJc4HuMUxH%2Fdq0hUmUUXVrYvkG3jP%2Ba%2FB6Vj3jI8qnQUILxADGgw3NDYxNjY3NTYyNjUiDFr71TsbnRtbNUX4ACr6BPGn9lOCsTm5n34KJtuhT33sBzsxQes0I2AamY%2B73JvPJMc08a%2FQ%2FqNM%2BtdxjDcxILd%2F%2BGgSeAJVl5WpSuyeG1gs3cpOkcQY6yvsm8PoCHlb5EyugCp2oDmm6HgMj%2Bkuf4IXODgZmohgtRNculKigHe2c9i7A5w4V2psD4YYdjBQBZFApSaKd%2BhloX3ctD7m0KGaeKxJeJzjD1zN5wBzmGh8UAY1a6iLSont%2BHnljSQsZu9U7iDe99%2FSxcbWZL2nEjvjyVENN%2B94m1a3yLeWg8WpaLpcPj%2FUeBHA&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20251022T132901Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3599&X-Amz-Credential=ASIA23OYBEOUZHYDRAAJ%2F20251022%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=b165c66043b59fac66d120144f8e51f","version":2}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCleanAttr","params":{"type":"global"}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapInfo"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/details
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/settings
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCleanAttr","params":{"type":"running"}},{"method":"getAreaUnit"},{"method":"getCleanRecords"},{"method":"getDoNotDisturb"},{"method":"getConsumablesInfo"},{"method":"getMopState"},{"method":"getDeviceTime"},{"method":"getCurrentVoiceLanguage"},{"method":"getCustomizationRulesInfo"}]}}},"serviceId":"passthrough"}
------------------------
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/voice-packages?deviceModel=RV30%20Max%20Plus
Method: GET
Headers:

logReq err: TypeError: not a function
[Socket.connect] addr=aps1-app-server.iot.i.tplinkcloud.com/54.254.71.245:443 timeout=30000
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/rt-info
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCleanAttr","params":{"type":"global"}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/firmwares/latest
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"startUpdateMapData","params":{"map_upload_start":true}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"startUpdateMapData","params":{"map_upload_start":true}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapInfo"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{"map_id":0}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCleanRecords"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCleanRecords"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/rules?ruleType=schedule&startIndex=0
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/firmwares/latest
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getDeviceTime"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/settings
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/details
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCarpetClean"},{"method":"getChildLockInfo"},{"method":"getDoNotDisturb"},{"method":"getCleanAttr","params":{"type":"pose"}},{"method":"getConsumablesInfo"},{"method":"getDustCollectionInfo"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapInfo"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/rt-info
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{"map_id":0}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapInfo"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/details
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/shadows?thingNames=<REDACTED>
Method: GET
Headers:
isEdgeRequest: false

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/settings
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/voice-packages?deviceModel=RV30%20Max%20Plus
Method: GET
Headers:

logReq err: TypeError: not a function
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getMapData","params":{"map_id":0}},{"method":"getPathData","params":{"start_pos":0}}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/services-sync
Method: POST
Headers:
isEdgeRequest: false

Body:
{"inputParams":{"requestData":{"method":"multipleRequest","params":{"requests":[{"method":"getCleanAttr","params":{"type":"running"}},{"method":"getAreaUnit"},{"method":"getCleanRecords"},{"method":"getDoNotDisturb"},{"method":"getConsumablesInfo"},{"method":"getMopState"},{"method":"getDeviceTime"},{"method":"getCurrentVoiceLanguage"},{"method":"getCustomizationRulesInfo"}]}}},"serviceId":"passthrough"}
------------------------
---- OKHTTP REQUEST ----
URL: https://aps1-app-server.iot.i.tplinkcloud.com/v1/things/<REDACTED>/rt-info
Method: GET
Headers

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal That's great, I do see something in your logs about nbu, that may help point me where I need to go.

Yea, Frida is a pain. I've used it in the past but lost my devices to do so, so it's been rough lately.

I appreciate your work so far. I am going through the encryption pieces again. Yea, the pake_register phase is essentially handshake1 and I still need to see what is going on.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal Ok, I've spent most of the day going over things, and I think I'm at an impasse. I can't get the URLs to communicate with the cloud for the NOC certificates. I've gotten close, but what happens is that the APK uses a call, gets a cloud token, then pulls the URLs from the cloud. I can get the cloud token, but the I cannot get the signature matches for the calls out that have 'signature-required' set to true. I need to see if there is a way to reverse engineer these signatures so we can get them to match from my code based on how everything is supposed to be. I have some scripts and code that I've got for testing if you would be able to take it along with some of the Frida work you've done and possibly see if you can get the URL? I can send them via Discord if that works?

@danieyal

Copy link
Copy Markdown

@ZeliardM yeah sure, I will try but since I am working during the day, I might not have enough time to dedicate to it, but I'll try. Discord works for me.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal All good, give it a shot and let me know and we will go from there. Thanks!

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal What I have been able to pull apart today is that the checkpassword call that is used in the frida logs you posted earlier, this has the same signature requirements that I am looking for. If you can get me some wireshark pulls of this actual communication so I can see the actual headers and packet information, then I might be able to reverse engineer this from there. If you want, we can keep going on Discord, my username is the same there.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@danieyal nevermind, I finally got the matching signature and figured out how to get the right url for getting the certificates!

@ZeliardM

ZeliardM commented Oct 24, 2025

Copy link
Copy Markdown
Collaborator Author

Ok, now that I have all of this, my plan is as follows:

  1. Add certificate handling and a certificate store to the python-kasa API and CLI. This will be both as callable functions and storage. python-kasa itself is stateless and does not hold information, so these will be configured to store data in predefined areas based on Operating Systems.

  2. Update the TPAP Transport to handle NOC Authentication with SPAKE2+ and DAC Authentication. This will use the NOC data from the new modules to handle everything hopefully seamlessly, at least that's how I'm going to try to get it configured.

It's going to take quite some time for me to get through all of this, I want to put it together cleanly.

I'm headed out of town with my family for the weekend so I may not work on it much right now, but will keep everyone updated as I keep working on it.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

I am still slowly working on this. I have the NOC capabilities set up, now I'm working on testing the code. Having issues with the CSR formatting so I'll keep plugging away at it and keep you in the loop.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

@tuck-ski Thaks for this feedback. I haven't had anyone test this path yet so it's good to see. I'll take this information and see if I can work on getting the issues resolved and let you know. Thanks!

@WoodenDuke

Copy link
Copy Markdown

If anyone with a Tapo RV30 or RV20 is tired of waiting (and - after about 7 months of "I've almost got it" - no longer believes a solution is coming, like I did)...

There is an alternative solution using the HA integration by epg-pers:
https://github.com/epg-pers/tapo-rv30-ha

Using this, I got my TAPO RV20 Max Plus up and running again in just a few minutes.

Maybe this helps someone out there.

@realugbun

Copy link
Copy Markdown

Tested this branch working end-to-end on a P125M, no KLAP regression

Built feature/tpap (@2640c28) into a clean venv on top of the 0.10.2 base (Python 3.11), alongside Home Assistant core 2026.5.x which still bundles python-kasa 0.10.2.

Background / why this matters now: TP-Link has been pushing firmware auto-updates that migrate plugs from KLAP to TPAP. Once a device updates, it stops speaking the old auth entirely. It still works in the Tapo app, but silently drops off Home Assistant (shows unavailable while the device is clearly online and pingable). I've now had a batch of P125M plugs go dark this way, so for anyone on auto-update this is becoming an active outage, not a theoretical gap. Disabling auto-update only partially mitigates (TP-Link can still push server-side).

Repro of the pre-PR failure

A P125M that auto-updated to firmware 1.4.2 Build 251204 now advertises:
EncryptionScheme(is_support_https=False, encrypt_type='TPAP', http_port=80, lv=2)

On stock 0.10.2, Discover.discover_single() raises UnsupportedDeviceError for this device. (The device answers GET / and POST /app/handshake1 with a generic <html><body><center>200 OK</center></body></html> to KLAP clients, which is what surfaces in HA logs as unexpected klap response ... to handshake1.)

With this PR

  • discover_single() correctly identifies DeviceEncryptionType.Tpap.
  • Device.connect()update() succeeds; TPAP PAKE handshake completes.
  • turn_on() / turn_off() both work and read back correctly. Fully local, no cloud.

Useful finding for the migration/re-auth path

The existing KLAP credentials_hash cannot authenticate under TPAP. Connecting with a stored credentials_hash (no cleartext) fails at the pake_share step:
DeviceError: TPAP pake_share failed: INTERNAL_UNKNOWN_ERROR(-100000)
(device-level error code -2203). Supplying the cleartext account credentials via Credentials(username, password) succeeds. This implies that when this lands in HA, upgraded devices will need a re-auth (password re-entry). Reusing the stored hash won't be sufficient. Might be worth calling out in the integration migration / docs so it's a graceful reauth flow rather than a silent failure.

KLAP regression check (since this touches shared protocol code)

Same build, against devices still on KLAP:

  • Read OK on P210M (hw 1.0), firmware 1.3.1 Build 250416 Rel.090619 (DeviceEncryptionType.Klap). Tested on 3 separate units.
  • Read and write (toggle off→on→restore) OK on a P110M (hw 1.0), firmware 1.3.2 Build 250526 Rel.171305.
    No errors, so the KLAP path looks unaffected.

Thanks for the work on this. I'm happy to run additional tests on P125M / firmware 1.4.2 hardware if it'd help.

@Pacha-Icefree

Copy link
Copy Markdown

RV30 Max TPAP test results

I tested this PR branch against a Tapo RV30 Max and the TPAP transport works for read-only operations when the protocol/device family is explicitly forced.

Test environment

  • Device: RV30 Max(UN)
  • Device type: SMART.TAPOROBOVAC
  • Firmware: 1.3.3 Build 260206 Rel.091753
  • Hardware: 1.0
  • Host: <REDACTED_LOCAL_IP>
  • Branch tested: feature/tpap / PR #1592
  • Credentials: cleartext username/password were used for the test
  • Test mode: read-only commands only

Discovery result

discover raw succeeds and reports TPAP:

device_type: SMART.TAPOROBOVAC
device_model: RV30 Max(UN)
tpap:
  tls: 2
  dac: 1
  noc: 1
  pake:
    - 2
  port: 4433
mgt_encrypt_schm:
  is_support_https: true
  http_port: 4433
  encrypt_type: TPAP
  lv: 2

Commands tested successfully

The following commands completed successfully when forcing the device family and TPAP connection parameters:

  uv run kasa --host <REDACTED_LOCAL_IP> \
    -df SMART.TAPOROBOVAC \
    -e TPAP \
    --https \
    --port 4433 \
    --timeout 20 \
    --username "<REDACTED_USERNAME>" \
    --password "<REDACTED_PASSWORD>" \
    state
  uv run kasa --host <REDACTED_LOCAL_IP> \
    -df SMART.TAPOROBOVAC \
    -e TPAP \
    --https \
    --port 4433 \
    --timeout 20 \
    --username "<REDACTED_USERNAME>" \
    --password "<REDACTED_PASSWORD>" \
    feature
  uv run kasa --host <REDACTED_LOCAL_IP> \
    -df SMART.TAPOROBOVAC \
    -e TPAP \
    --https \
    --port 4433 \
    --timeout 20 \
    --username "<REDACTED_USERNAME>" \
    --password "<REDACTED_PASSWORD>" \
    feature vacuum_status
  uv run kasa --host <REDACTED_LOCAL_IP> \
    -df SMART.TAPOROBOVAC \
    -e TPAP \
    --https \
    --port 4433 \
    --timeout 20 \
    --username "<REDACTED_USERNAME>" \
    --password "<REDACTED_PASSWORD>" \
    feature mop_waterlevel
  uv run kasa --host <REDACTED_LOCAL_IP> \
    -df SMART.TAPOROBOVAC \
    -e TPAP \
    --https \
    --port 4433 \
    --timeout 20 \
    --username "<REDACTED_USERNAME>" \
    --password "<REDACTED_PASSWORD>" \
    feature carpet_boost
  uv run kasa --host <REDACTED_LOCAL_IP> \
    -df SMART.TAPOROBOVAC \
    -e TPAP \
    --https \
    --port 4433 \
    --timeout 20 \
    --username "<REDACTED_USERNAME>" \
    --password "<REDACTED_PASSWORD>" \
    vacuum records list

Observed working features

state and feature expose the following successfully:

  Vacuum status: Charging
  Vacuum error: Ok
  Battery level: available
  Fan speed: Quiet / Standard / Turbo / Max / Ultra
  Clean count: available
  Carpet boost: available
  Mop attached: available
  Mop water level: Disable / Low / Medium / High
  Clean area: available
  Clean time: available
  Clean progress: available
  Consumables:
    main brush remaining / used / reset action
    side brush remaining / used / reset action
    filter remaining / used / reset action
    sensor remaining / used / reset action
    charging contacts remaining / used / reset action
  Cleaning records: available via `vacuum records list`

Specific read-only feature checks also worked:

feature vacuum_status -> 5
feature mop_waterlevel -> Disable
feature carpet_boost -> True

vacuum records list also returned valid historical cleaning records and total statistics.

Matter warning

The device reports UNKNOWN_METHOD_ERROR for Matter setup info:

get_matter_setup_info for Matter (error_code=UNKNOWN_METHOD_ERROR)

This does not block the TPAP transport, state, feature, consumables, or cleaning records.

Important observation

If I run commands without forcing the connection parameters, some commands may fail with:

TPAP pake_share failed: INTERNAL_UNKNOWN_ERROR(-100000)
device error code: -2203

Forcing the following parameters made the read-only commands work reliably:

  -df SMART.TAPOROBOVAC
  -e TPAP
  --https
  --port 4433

I also avoided running multiple commands in parallel. Sequential execution with a short delay between commands was more stable.

Not tested yet

I intentionally did not run commands that modify the real robot state, such as:

vacuum_start
vacuum_pause
vacuum_return_home
changing fan speed
changing mop water level
changing clean count
resetting consumables

I can run those write/action tests if maintainers want additional validation on RV30 Max hardware.

@tamashi84

Copy link
Copy Markdown

Wanted to share a real-world validation of this PR.

I have a 36-device Tapo smart home running Home Assistant on a Synology NAS via Docker. After the 1.4.2 firmware update rolled out across my P125M plugs, KL125 bulbs, and KL400L5 strips, everything broke — both Home Assistant and my H500 hub automations stopped working entirely.

I manually patched the python-kasa 0.10.2 installation inside my HA Docker container tonight using the files from this PR:

  • tpaptransport.py
  • device_factory.py (TPAP transport registration)
  • deviceconfig.py (added Tpap to DeviceEncryptionType enum)
  • transports/init.py (exported TpapTransport)
  • Also had to pip install passlib as an additional dependency

After patching and re-adding the affected P125M devices through the HA UI, everything came back online. Full control restored over all TPAP devices on firmware 1.4.2.

Tested against: P125M (firmware 1.4.2 Build 251204 Rel.103056)
HA version: 2026.6
python-kasa version patched: 0.10.2

This PR works. Please merge.

@realugbun

Copy link
Copy Markdown

I understand this PR is not fully functional on some devices like the rv30 vacuum. However, right now basic things on Home Assistant like smart plugs are completely broken. Two testers have validated smart plugs work with the new code and there is no regression on KLAP.

There are currently likely thousands of users with broken smart homes. I would propose you merge this PR and open a second for advanced device support.

@ZeliardM

Copy link
Copy Markdown
Collaborator Author

This PR should handle read and write functionality. This is just a communication piece, nothing with the actual commands has changed. If the communication is there and the existing commands worked before, they should work now. Again, I don't have a device to test so someone would need to confirm but it should work.

The authentication regression is the fact that some devices respond to discovery calls on both UDP and TDP and if UDP wins out before the new TDP it will fail back to KLAP, that's a discovery regression, not an issue with this PR and is to be handled separately in several other PRs I have also started.

I appreciate everyone commenting and validating information for this PR, it's good to know for most everyone it is working as intended. What everyone needs to understand is, the work is here, it's in place, there are just other things in life that have come up for the maintainers and myself and this is a personal side, helping the community project. We all understand the importance of this in everyone's smart home designs and functionality and not trying to belittle that in any way.

The fixes will come full circle, just not sure when yet.

@Pacha-Icefree

Pacha-Icefree commented Jun 12, 2026

Copy link
Copy Markdown

Tapo RV30 Max TPAP Validation Report

I ran additional validation against a Tapo RV30 Max using this PR branch.

Context

  • Device: Tapo RV30 Max
  • Protocol path: TPAP over HTTPS
  • Test environment: local Linux checkout of this PR
  • Credentials, host and private network details intentionally omitted
  • Risky/destructive tests were intentionally excluded, including consumable resets

The goal was to validate both reversible write operations and supervised motion controls without requiring any cloud/app interaction.

Test Results

Area Test Result Notes
Discovery UDP discovery without credentials Partial The device was discovered as RV30 Max / SMART.TAPOROBOVAC / TPAP / HTTPS / login version 2, but the follow-up authenticated update failed as expected without credentials
Discovery UDP discovery with credentials OK The device was discovered and authenticated successfully; private network details intentionally omitted
Connection TPAP communication OK Device was reachable and commands executed successfully
Read vacuum_status OK Status values were readable before and after actions
Write vacuum_fan_speed change and restore OK Changed from current value to a temporary value and restored successfully
Write mop_waterlevel change and restore OK Changed from current value to a temporary value and restored successfully
Write clean_count change and restore OK Changed from current value to a temporary value and restored successfully
Write carpet_boost change and restore OK Changed from current value to a temporary value and restored successfully
Motion Start cleaning OK vacuum_start executed and reported vacuum_status=1
Motion Pause cleaning OK vacuum_pause executed and reported vacuum_status=7
Motion Resume cleaning OK vacuum_start resumed cleaning and reported vacuum_status=1
Motion Return home OK vacuum_return_home executed and reported vacuum_status=4
Matter module Matter setup info Partial get_matter_setup_info returned UNKNOWN_METHOD_ERROR; this did not block TPAP operation
Consumable reset operations Not executed - Intentionally skipped to avoid destructive/irreversible changes

Observed Status Mapping

  • 1: Cleaning
  • 4: Going home
  • 6: Charged
  • 7: Paused

Summary

From these tests, the TPAP implementation in this PR is working for the main practical control paths on the RV30 Max: connection, status reads, reversible setting writes, start, pause, resume and return-home.

UDP discovery was also validated. The device was correctly identified as an RV30 Max using the SMART.TAPOROBOVAC family, TPAP encryption, HTTPS enabled and login version 2. Discovery without credentials can identify the device but cannot complete the authenticated follow-up update, while discovery with credentials completes successfully.

I can run more targeted tests if maintainers need additional coverage, but from a user-impact perspective this already enables a meaningful and currently missing feature set for RV-series vacuums.

My recommendation would be to move this PR forward rather than waiting for every possible edge case to be perfect. The perfect is the enemy of the good, and this version already provides working TPAP support for real hardware while leaving room for follow-up improvements.

@awood-ops

Copy link
Copy Markdown

Tested on L900-5(EU) — pake_share fails with -100000

Device discovery:
"tpap_preferred": true,
"tpap": { "tls": 0, "dac": 0, "noc": 0, "pake": [2], "port": 80 },
"mgt_encrypt_schm": { "is_support_https": false, "http_port": 80, "encrypt_type": "TPAP", "lv": 2 }

Steps that succeed: discover (via /), pake_register (device returns valid extra_crypt, dev_salt, dev_share, cipher_suites: 1, iterations: 3000).

pake_register response:
{
"extra_crypt": { "type": "password_shadow", "params": { "passwd_id": 2 } },
"encryption": "aes_128_ccm",
"cipher_suites": 1,
"iterations": 3000
}

pake_share fails: INTERNAL_UNKNOWN_ERROR(-100000) — the device rejects our proof.

I tried the following credential candidates (all fail):

  • Raw Tapo account password
  • md5_hex(password)
  • sha256_hex_upper(password)
  • MAC-derived password via _mac_pass_from_device_mac

The Tapo account ownership is confirmed (MD5(email) matches the device owner field). The same dev_salt appears on both L900-5 strips on the network, which may be relevant.

The RV30 Max that was successfully tested has dac: 1, noc: 1. This device has dac: 0, noc: 0. Worth checking whether the _use_dac_certification() or credential derivation paths differ for those flags.

@realugbun

Copy link
Copy Markdown

@awood-ops

Running this PR against 9x TP-Link P125M smart plugs (fw 1.4.2 Build 251204) and all nine authenticate successfully.

I instrumented TpapEncryptionSession._resolve_credentials to dump the pake_register result on a working P125M. It's identical to your failing L900-5 in every crypto-relevant field:

extra_crypt:   {"type": "password_shadow", "params": {"passwd_id": 2}}
iterations:    3000
cipher_suites: 1
pake:          [2]
encryption:    aes_128_ccm
passcode_type: userpw

The only difference is the dac flag:

tpap.dac _use_dac_certification() pake_share result
P125M (works) 1 True (adds dac_nonce) session established
L900-5 (yours) 0 False (no dac_nonce) -100000

Two things worth noting:

1. The passwd_id == 2 credential derivation is fine With passwd_id: 2, _build_credentials returns _sha1_hex(passcode) and my device accepts it. The md5_hex/sha256_hex_upper candidates don't apply here. A non-camera userpw device, _get_candidate_secrets only returns [password] and the shadow gets applied in _build_credentials. That path probably isn't your problem.

2. The bug is in the non-DAC pake_share path. Everything gated on _use_dac_certification() is just this:

if self._use_dac_certification():
    self._dac_nonce_base64 = self._base64(secrets.token_bytes(16))
    share_params["dac_nonce"] = self._dac_nonce_base64

All the working devices seem to have dac:1 and goes through that branch. The dac:0 path hasnt produced a working session for me. My guess is the device expects something different in pake_share when there's no DAC nonce, not just the field missing entirely.

All my hardware is dac:1 so I can't test this directly, but the working handshake is there if you want a reference to try against.

@rhanekom

rhanekom commented Jun 26, 2026

Copy link
Copy Markdown

This PR works. Please merge.

I tested with a shim - possible because the pinned version is the same = 0.10.2.

Custom component which is effectively a no-op, with manifest.json:

"requirements": [
    "python-kasa[speedups] @ https://github.com/ZeliardM/python-kasa/archive/2640c2848a8f671ca29f92ab01d1c0b7385a2d6a.zip"
  ],

I can finally add my sets of P110 plugs that I just heavily invested in with this PR. Firmware 1.4.3 Build 251223 Rel. 100616.

One oddity is that I need to re-authenticate with my Tapo credentials on each restart. Otherwise works like a charm.

@ZeliardM

ZeliardM commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

@realugbun @awood-ops Thanks for the reports. I have re-investigated the APK code and it seems that there are not changes from the APK side for TPAP handling lately. When dac = 0, the entire dac_nonce is completely gated and left out and not used. If dac = 1, then it is created according to the specifications and passed to the pake_register and then the pake_result is handled in that respect. dac, or Device Attestation Certificate, is a local certificate that is registered on the device, then verified by the connection to make sure that the certificate was issues by TPLink correctly and that it matches the device it's registered to and the user account. I have made some recent changes to the code. If you could check the devices again that do not have dac and then, if possible, get me full debug logs if it fails again and let me know. You can send them via Discord as well, my handle there is the same. Another thing you could try and do is completely remove the device that is failing from your account and re-add it using the device selection method instead of auto-discovery method. Also, if you have Third-Party Compatibility turned on, you can try disabling it. Or disabling and re-enabling it. Sometimes this will work and let the older devices use their older authentication instead of TPAP, but if you want TPAP, then I would turn it on and then turn it back off and see if that kind of resets the devices communication on their end. The new commit will be out shortly and you can try then.

@ZeliardM

ZeliardM commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Ok, everyone. The new commits are out here, let's give them a shot and let's see if everything is still functioning like it should. The hub handling should be back instead of forcing camera auth. We've also cleaned up a lot of other pieces and hopefully getting really close to a release. I know it's been a really long time and it's very close to coming out.

@ZeliardM

ZeliardM commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Ok, a couple more credential handling things as well were just pushed. Please test the changes and let me know how things work everyone!

@danieyal

Copy link
Copy Markdown

@ZeliardM tested on my end with my RV30 and P110, and it still works great

@realugbun

Copy link
Copy Markdown

Retest on latest commit (e7084472) — TPAP works, no KLAP/legacy regression

Retested the 2026-07-09 commits against real hardware. TPAP still authenticates and controls my P125M plugs, and I see no regression on my KLAP (P110M) or legacy XOR (HS200) devices. The hub-handling / camera-auth cleanup looks good: the TPAP plugs resolve to the correct transport with no forced camera auth.

Setup: local checkout of feature/tpap @ e7084472, python-kasa[speedups] 0.10.2, credentials provided.

TPAP — P125M (fw 1.4.4 Build 260521)

  • Discovery + auth: ✅ (tpap: {dac:1, noc:1, pake:[2], port:80, tls:0}, tpap_preferred: true)
  • Handshake: starting → complete, transport SMART.TPAP — no camera-auth path in debug logs
  • State read: ✅
  • Control (on → off → on): ✅ reversible, state confirmed at each step

KLAP — P110M (fw 1.3.2 Build 250526)

  • Auth, transport SMART.KLAP: ✅
  • State + energy read: ✅ (voltage/current/power all returned)
  • Control (LED config write, toggled and restored): ✅

Legacy XOR — HS200 (fw 1.0.10 Build 230518)

  • Auth, transport IOT.XOR: ✅
  • State read: ✅
  • Control (on → off): ✅ reversible

No errors or exceptions on any protocol. Note: my P125M units are now on fw 1.4.4 (up from 1.4.2 in my earlier report) and still authenticate fine.

Working as intended. No regressions.

Thank you for taking this up. Let me know if you need any further tests.

@rhanekom

Copy link
Copy Markdown

Ok, a couple more credential handling things as well were just pushed. Please test the changes and let me know how things work everyone!

@ZeliardM credentials recalled on reboots, and P110 plugs working perfectly. Thanks!

@Pacha-Icefree

Copy link
Copy Markdown

RV30 Max validation update for PR #1592

Tested against the current feature/tpap branch:

e708447 Retry TPAP sessions on stat access errors

Device under test:

Tapo RV30 Max / SMART.TAPOROBOVAC / TPAP over HTTPS

Static checks

Check Result
Clean checkout at e708447 OK
Local RV30 test scripts: bash -n OK
Local RV30 test scripts: shellcheck OK
Destructive command scan in local RV30 scripts OK
Relevant feature IDs present in code OK
Key TPAP / clean / mop modules compile OK
pytest tests/smart/modules/test_clean.py tests/smart/modules/test_mop.py OK, 44 passed

Relevant feature IDs confirmed in the PR code:

vacuum_status
vacuum_error
vacuum_fan_speed
vacuum_start
vacuum_pause
vacuum_return_home
clean_count
carpet_boost
mop_attached
mop_waterlevel

Runtime results on RV30 Max

Area Result Notes
Read-only state/features OK state, feature, individual feature reads all succeeded
Retry/session reads OK 3 delayed vacuum_status reads succeeded after 120s waits
Discovery list without credentials OK Device detected as RV30 Max / SMART.TAPOROBOVAC / TPAP / HTTPS / login version 2
Discovery list with credentials OK Authenticated discovery succeeded
Discovery detail with credentials OK Full detail completed and found 1 device
Cleaning records OK vacuum records list succeeded
Reversible writes OK Settings changed and restored successfully
Motion commands Partial Robot executes the commands, but CLI returns an error after execution
Matter module Partial get_matter_setup_info returns UNKNOWN_METHOD_ERROR; TPAP operation is unaffected

Reversible write details

Feature Result Roundtrip
vacuum_fan_speed OK Turbo -> Standard -> Turbo
mop_waterlevel OK Medium -> Disable -> Medium
clean_count OK 2 -> 1 -> 2
carpet_boost OK True -> False -> True

Motion command details

The robot accepted and executed the motion commands, confirmed by status reads:

Action Status after action Result
Initial state 6 OK
vacuum_start 1 OK at device level
vacuum_pause 7 OK at device level
Resume via vacuum_start 1 OK at device level
vacuum_return_home 4, later 5 OK at device level

However, each action command returned this CLI error after the robot had already accepted the command:

Raised error: expected string or bytes-like object, got 'dict'

This looks like a CLI formatting issue for action responses, not a TPAP transport/device-control failure. The likely code path is kasa/cli/feature.py, where action features call echo(response) and the response is a dict.

Conclusion

For RV30 Max, the current PR works for the practical TPAP paths:

  • Discovery
  • Authenticated communication
  • State and feature reads
  • Cleaning records
  • Delayed/repeated reads for retry/session behavior
  • Reversible setting writes
  • Start, pause, resume and return-home at the device level

The only issue found is the CLI action-response formatting error above. I would not consider that a blocker for the TPAP implementation itself; it can be handled as a small CLI follow-up fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.