Skip to content

Tapo P110M Authenticate Failure #1353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gebjeff opened this issue Dec 9, 2024 · 14 comments
Open

Tapo P110M Authenticate Failure #1353

gebjeff opened this issue Dec 9, 2024 · 14 comments

Comments

@gebjeff
Copy link

gebjeff commented Dec 9, 2024

I have a Tapo H200 and two Tapo P110M plugs. All three devices have been added to the Tapo app and the H200 and one P110M were successfully integrated with HA (i.e., successfully authenticates to tplinkcloud.com). The second P110M fails to authenticate even though I'm using the same account name and password.

I've taken the following actions:

  1. Performed several factory resets to the problematic P110M.
  2. Deleted and successfully readded both P110Ms to the Tapo app.
  3. Deleted and successfully readded the H200 and the working P110M to the HA integration.
  4. Unsuccessfully tried to add the problematic P110M several times and authentication failed both times.

I've attached the debug log.
debug log.txt

@rytilahti
Copy link
Member

Could you try rebooting the device to see if that helps? Some users have reported that a reboot (or removing and replugging the device) has suddenly made their devices work.

@gebjeff
Copy link
Author

gebjeff commented Dec 10, 2024 via email

@Thireus
Copy link

Thireus commented Feb 25, 2025

This issue is also tracked on this ticket:

home-assistant/core#138376

I own several P110M which I bought a few days ago, all run the same latest firmware and under the same account. They were all configured within the same 30min via the Tapo app. Only the very first one that I have set up allows me to authenticate successfully, all the others fail.

kasa --debug --host 192.168.xxxx --username xxx --password xxxx only works for the very first P110M device I've added to the app. For all the others I get "Server response doesn't match our challenge on ip".

I have also tried to remove one of the devices from the app, even reset it to factory default (long press the power button) - done several times. Nothing works. The above command will only be able to successfully authenticate against the only P110M in my account that works, but not the others.

Edit:

One difference I've noticed is that the one that accepts the auth states 'obd_src': 'tplink' while all the others which are not working have 'obd_src': 'tss' (information displayed when running --debug.

@Thireus
Copy link

Thireus commented Feb 25, 2025

According to the Tapo Android app decompiled code, there is some evidence that there is a special treatment for "tss" (TP-Link Simple Setup) Onboarding Source devices.

        boolean z11 = "amazon_atr".equals(obdSrc) || "tss".equals(obdSrc);
        boolean z12 = tDPIoTDevice.getEncryptType() == EnumEncryptType.KLAP;
        if ((z11 || z12) && tCAccountBean.getLocalUserName() != null) {
            h11 = hi0.a.h(hi0.a.h(tCAccountBean.getLocalUserName()) + "_" + com.tplink.libbasenetwork.utils.h.a(tDPIoTDevice.getMac()));
        } else {
            h11 = tCAccountBean.getLocalUserName() + "_" + com.tplink.libbasenetwork.utils.h.a(tDPIoTDevice.getMac());
        }

and

    public static byte[] g(byte[] bArr) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("md5");
        messageDigest.update(bArr);
        return messageDigest.digest();
    }

    public static String h(String str) {
        try {
            return l(g(str.getBytes()));
        } catch (NoSuchAlgorithmException e11) {
            yh0.a.e(e11.toString());
            return str;
        }
    }

...

    public static String l(byte[] bArr) {
        StringBuilder sb2 = new StringBuilder();
        for (byte b11 : bArr) {
            String hexString = Integer.toHexString(b11 & 255);
            if (hexString.length() == 1) {
                sb2.append(AutoSwitchDate.DISABLE);
                sb2.append(hexString);
            } else {
                sb2.append(hexString);
            }
        }
        return sb2.toString();
    }

This is the relevant code.

Edit: Not sure if/where this is used at this stage... or if this even related to how the client hash gets computed

@barney34
Copy link

@Thireus is this fixable I havent a clue. Trying to prevent myself from going to another device.

@Thireus
Copy link

Thireus commented Feb 25, 2025

@barney34, yes this is fixable. Need to reverse the device firmware (Tapo apps don't appear to use the local KLAP protocol... they communicate with the cloud via TLS) and implement the same algorithm that computes the hash for these "tss" devices. Can you confirm your device also shows "tss" when you --debug?

This is the line of code that should trigger:

if local_seed_auth_hash == server_hash:
_LOGGER.debug("handshake1 hashes match with expected credentials")
return local_seed, remote_seed, self._local_auth_hash # type: ignore

But it doesn't because the local_seed_auth_hash is different from the server_hash. Which to me would suggest the local_seed_auth_hash may be computed incorrectly of these "tss" devices. My guess is that the credentials need to be in a different format.

@barney34
Copy link

@Thireus I dont know how to do that but if you tell me I can figure it out

@Thireus
Copy link

Thireus commented Feb 25, 2025

@barney34

pip install python-kasa
kasa --debug --host 192.168.xxxx --username 'xxxxxx' --password 'xxxxxx'

Where host is you device's IP, username and password are your tapo account credentials.

@barney34
Copy link

barney34 commented Feb 25, 2025

@Thireus when my work I get a full dump when mine goes offline the IP I can still ping but cant not run the debug against the device it in some terrible state. I have to reboot it to get it back even the local power and or dimmers wont work but the IP I can ping

Discovering device 192.168.100.125 for 10 seconds

DEBUG [DISCOVERY] 192.168.100.125 >> {'system': {'get_sysinfo': {}}} discover.py:326
DEBUG Waiting a total of 10 seconds for responses... discover.py:587
DEBUG Using IotPlug for IOT.SMARTPLUGSWITCH device_factory.py:179
DEBUG Finding protocol for 192.168.100.125 device_factory.py:194
DEBUG Finding protocol for DeviceFamily.IotSmartPlugSwitch device_factory.py:197
DEBUG Finding transport for IOT.KLAP device_factory.py:226
DEBUG Created KLAP transport for 192.168.100.125 klaptransport.py:141
DEBUG [DISCOVERY] 192.168.100.125 << {'error_code': 0, discover.py:924
'result': {'device_id': 'REDACTED_0387f481d44611a9ec9034b',
'device_model': 'ES20M(US)',
'device_type': 'IOT.SMARTPLUGSWITCH',
'factory_default': False,
'hw_ver': '1.0',
'ip': '192.168.100.125',
'mac': '40-ED-00-00-00-00',
'mgt_encrypt_schm': {'ANS': False,
'encrypt_type': 'KLAP',
'http_port': 80,
'is_support_https': False,
'lv': 2,
'new_klap': 1},
'obd_src': 'tplink',
'owner': 'REDACTED_678F6E96F0E783E0EA8977B',
'protocol_version': 1}}
DEBUG Initializing 192.168.100.125 of type <class 'kasa.iot.iotplug.IotPlug'> device.py:212
DEBUG Performing the initial update to obtain sysinfo iotdevice.py:314
DEBUG 192.168.100.125 >> {"system":{"get_sysinfo":{}}} iotprotocol.py:143
DEBUG Starting handshake with 192.168.100.125 klaptransport.py:317
DEBUG Posting to http://192.168.100.125/app/handshake1 httpclient.py:88
DEBUG Device 192.168.100.125 received an os error, enabling sequential request delay: [Errno 104] Connection reset by peer httpclient.py:136
DEBUG 192.168.100.125 >> {"system":{"get_sysinfo":{}}} iotprotocol.py:143
DEBUG Starting handshake with 192.168.100.125 klaptransport.py:317
DEBUG Device 192.168.100.125 waiting 0.24917759899994962 seconds to send request httpclient.py:81
DEBUG Posting to http://192.168.100.125/app/handshake1 httpclient.py:88
DEBUG 192.168.100.125 >> {"system":{"get_sysinfo":{}}} iotprotocol.py:143
DEBUG Starting handshake with 192.168.100.125 klaptransport.py:317
DEBUG Device 192.168.100.125 waiting 0.24822860599988417 seconds to send request httpclient.py:81
DEBUG Posting to http://192.168.100.125/app/handshake1 httpclient.py:88
DEBUG 192.168.100.125 >> {"system":{"get_sysinfo":{}}} iotprotocol.py:143
DEBUG Starting handshake with 192.168.100.125 klaptransport.py:317
DEBUG Device 192.168.100.125 waiting 0.2486628260000998 seconds to send request httpclient.py:81
DEBUG Posting to http://192.168.100.125/app/handshake1 httpclient.py:88
DEBUG Giving up on 192.168.100.125 after 3 retries iotprotocol.py:91
Raised error: ('Device connection error: 192.168.100.125: [Errno 104] Connection reset by peer', ClientOSError(104, 'Connection reset by peer'))

@Thireus
Copy link

Thireus commented Feb 25, 2025

@barney34, I could be wrong but it looks like to me that you may have some firewall on your network blocking http communications to the device... try to access http://192.168.100.125/ to see if that works.

@barney34
Copy link

barney34 commented Feb 25, 2025

@Thireus it works in about 30 seconds when the device comes back on line. I can ping the device the whole time. I dont want to derail the conversation.

Discovering device 192.168.100.125 for 10 seconds
DEBUG [DISCOVERY] 192.168.100.125 >> {'system': {'get_sysinfo': {}}} discover.py:326
DEBUG Waiting a total of 10 seconds for responses... discover.py:587
DEBUG [DISCOVERY] 192.168.100.125 << {'system': {'get_sysinfo': {'active_mode': 'none', discover.py:740
'alias': '#MASKED_NAME#',
'brightness': 52,
'dev_name': 'Wi-Fi Smart Dimmer with sensor',
'deviceId': 'REDACTED_3547B7DCA5429DC92708C762180D397',
'err_code': 0,
'feature': 'TIM',
'hold_on': 1,
'hwId': 'REDACTED_F31AF2EA08CD6610E699746',
'hw_ver': '1.0',
'icon_hash': '',
'latitude_i': 0,
'led_off': 0,
'lnk_on': 1,
'longitude_i': 0,
'mac': '40:ED:00:00:00:00',
'max_thr': 1,
'mic_type': 'IOT.SMARTPLUGSWITCH',
'model': 'ES20M(US)',
'next_action': {'type': -1},
'obd_src': 'tplink',
'oemId': 'REDACTED_BABAA04B18C0033D0368A92',
'on_time': 0,
'preferred_state': [{'brightness': 97, 'index': 0},
{'brightness': 75, 'index': 1},
{'brightness': 50, 'index': 2},
{'brightness': 25, 'index': 3}],
'relay_state': 0,
'rssi': -50,
'status': 'new',
'sw_ver': '1.1.5 Build 241206 Rel.142818',
'updating': 0}}}
DEBUG Initializing 192.168.100.125 of type <class 'kasa.iot.iotdimmer.IotDimmer'> device.py:212
DEBUG Finding protocol for 192.168.100.125 device_factory.py:194
DEBUG Finding protocol for DeviceFamily.IotSmartPlugSwitch device_factory.py:197
DEBUG Finding transport for IOT.XOR device_factory.py:226
DEBUG Adding module <Module Schedule (schedule) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Usage (schedule) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Antitheft (anti_theft) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Time (time) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Cloud (cnCloud) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Led (system) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Motion (smartlife.iot.PIR) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module AmbientLight (smartlife.iot.LAS) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Dimmer (smartlife.iot.dimmer) for 192.168.100.125> iotdevice.py:221
DEBUG Adding module <Module Light (light) for 192.168.100.125> iotdevice.py:221
DEBUG Initial update, so consider supported: schedule iotmodule.py:73
DEBUG Adding query for <Module Schedule (schedule) for 192.168.100.125>: {'schedule': {'get_rules': {}, 'get_next_action': {}}} iotdevice.py:411
DEBUG Initial update, so consider supported: schedule iotmodule.py:73
DEBUG Adding query for <Module Usage (schedule) for 192.168.100.125>: {'schedule': {'get_realtime': {}, 'get_daystat': {'year': 2025, iotdevice.py:411
'month': 2}, 'get_monthstat': {'year': 2025}}}
DEBUG Initial update, so consider supported: anti_theft iotmodule.py:73
DEBUG Adding query for <Module Antitheft (anti_theft) for 192.168.100.125>: {'anti_theft': {'get_rules': {}, 'get_next_action': {}}} iotdevice.py:411
DEBUG Initial update, so consider supported: time iotmodule.py:73
DEBUG Adding query for <Module Time (time) for 192.168.100.125>: {'time': {'get_time': {}, 'get_timezone': {}}} iotdevice.py:411
DEBUG Initial update, so consider supported: cnCloud iotmodule.py:73
DEBUG Adding query for <Module Cloud (cnCloud) for 192.168.100.125>: {'cnCloud': {'get_info': {}}} iotdevice.py:411
DEBUG Adding query for <Module Led (system) for 192.168.100.125>: {} iotdevice.py:411
DEBUG Initial update, so consider supported: smartlife.iot.PIR iotmodule.py:73
DEBUG Adding query for <Module Motion (smartlife.iot.PIR) for 192.168.100.125>: {'smartlife.iot.PIR': {'get_config': {}, iotdevice.py:411
'get_adc_value': {}}}
DEBUG Initial update, so consider supported: smartlife.iot.LAS iotmodule.py:73
DEBUG Adding query for <Module AmbientLight (smartlife.iot.LAS) for 192.168.100.125>: {'smartlife.iot.LAS': {'get_config': {}, iotdevice.py:411
'get_current_brt': {}}}
DEBUG Initial update, so consider supported: smartlife.iot.dimmer iotmodule.py:73
DEBUG Adding query for <Module Dimmer (smartlife.iot.dimmer) for 192.168.100.125>: {'smartlife.iot.dimmer': {'get_dimmer_parameters': iotdevice.py:411
{}, 'get_default_behavior': {}}}
DEBUG Initial update, so consider supported: light iotmodule.py:73
DEBUG Adding query for <Module Light (light) for 192.168.100.125>: {} iotdevice.py:411
DEBUG 192.168.100.125 >> iotprotocol.py:143
{"system":{"get_sysinfo":{}},"schedule":{"get_rules":{},"get_next_action":{},"get_realtime":{},"get_daystat":{"year":2025,"month
":2},"get_monthstat":{"year":2025}},"anti_theft":{"get_rules":{},"get_next_action":{}},"time":{"get_time":{},"get_timezone":{}},
"cnCloud":{"get_info":{}},"smartlife.iot.PIR":{"get_config":{},"get_adc_value":{}},"smartlife.iot.LAS":{"get_config":{},"get_cur
rent_brt":{}},"smartlife.iot.dimmer":{"get_dimmer_parameters":{},"get_default_behavior":{}}}
DEBUG Device 192.168.100.125 sending query xortransport.py:79
{"system":{"get_sysinfo":{}},"schedule":{"get_rules":{},"get_next_action":{},"get_realtime":{},"get_daystat":{"year":2025,"month
":2},"get_monthstat":{"year":2025}},"anti_theft":{"get_rules":{},"get_next_action":{}},"time":{"get_time":{},"get_timezone":{}},
"cnCloud":{"get_info":{}},"smartlife.iot.PIR":{"get_config":{},"get_adc_value":{}},"smartlife.iot.LAS":{"get_config":{},"get_cur
rent_brt":{}},"smartlife.iot.dimmer":{"get_dimmer_parameters":{},"get_default_behavior":{}}}
DEBUG Device 192.168.100.125 query response received xortransport.py:91
DEBUG 192.168.100.125 << {'anti_theft': {'get_next_action': {'err_code': -2, iotprotocol.py:152
'err_msg': 'member not support'},
'get_rules': {'enable': 0,
'err_code': 0,
'rule_list': [],
'version': 2}},
'cnCloud': {'get_info': {'binded': 1,
'cld_connection': 1,
'err_code': 0,
'fwDlPage': '',
'fwNotifyType': -1,
'illegalType': 0,
'server': 'n-devs.tplinkcloud.com',
'stopConnect': 0,
'tcspInfo': '',
'tcspStatus': 1,
'username': 'user@example.com'}},
'schedule': {'get_daystat': {'day_list': [{'day': 1,
'month': 2,
'time': 107,
'year': 2025},
{'day': 2,
'month': 2,
'time': 72,
'year': 2025},
{'day': 3,
'month': 2,
'time': 100,
'year': 2025},
{'day': 4,
'month': 2,
'time': 117,
'year': 2025},
{'day': 5,
'month': 2,
'time': 130,
'year': 2025},
{'day': 6,
'month': 2,
'time': 190,
'year': 2025},
{'day': 7,
'month': 2,
'time': 113,
'year': 2025},
{'day': 8,
'month': 2,
'time': 122,
'year': 2025},
{'day': 9,
'month': 2,
'time': 163,
'year': 2025},
{'day': 10,
'month': 2,
'time': 99,
'year': 2025},
{'day': 11,
'month': 2,
'time': 104,
'year': 2025},
{'day': 12,
'month': 2,
'time': 872,
'year': 2025},
{'day': 13,
'month': 2,
'time': 126,
'year': 2025},
{'day': 14,
'month': 2,
'time': 91,
'year': 2025},
{'day': 15,
'month': 2,
'time': 103,
'year': 2025},
{'day': 16,
'month': 2,
'time': 119,
'year': 2025},
{'day': 17,
'month': 2,
'time': 365,
'year': 2025},
{'day': 18,
'month': 2,
'time': 83,
'year': 2025},
{'day': 19,
'month': 2,
'time': 137,
'year': 2025},
{'day': 20,
'month': 2,
'time': 249,
'year': 2025},
{'day': 21,
'month': 2,
'time': 479,
'year': 2025},
{'day': 22,
'month': 2,
'time': 10,
'year': 2025},
{'day': 23,
'month': 2,
'time': 93,
'year': 2025},
{'day': 24,
'month': 2,
'time': 137,
'year': 2025},
{'day': 25,
'month': 2,
'time': 67,
'year': 2025}],
'err_code': 0},
'get_monthstat': {'err_code': 0,
'month_list': [{'month': 1,
'time': 3763,
'year': 2025},
{'month': 2,
'time': 4248,
'year': 2025}]},
'get_next_action': {'err_code': 0, 'type': -1},
'get_realtime': {'err_code': -2, 'err_msg': 'member not support'},
'get_rules': {'enable': 1,
'err_code': 0,
'rule_list': [],
'version': 2}},
'smartlife.iot.LAS': {'get_config': {'devs': [{'dark_index': 0,
'enable': 1,
'hw_id': 0,
'level_array': [{'adc': 390,
'name': 'cloudy',
'value': 16},
{'adc': 300,
'name': 'overcast',
'value': 12},
{'adc': 222,
'name': 'dawn',
'value': 9},
{'adc': 222,
'name': 'twilight',
'value': 9},
{'adc': 111,
'name': 'total '
'darkness',
'value': 5},
{'adc': 2400,
'name': 'custom',
'value': 95}],
'max_adc': 2550,
'min_adc': 0}],
'err_code': 0,
'ver': '1.0'},
'get_current_brt': {'err_code': 0, 'value': 5}},
'smartlife.iot.PIR': {'get_adc_value': {'err_code': 0, 'value': 2096},
'get_config': {'array': [80, 50, 20, 0],
'cold_time': 600000,
'enable': 1,
'err_code': 0,
'max_adc': 4095,
'min_adc': 0,
'trigger_index': 1,
'version': '1.0'}},
'smartlife.iot.dimmer': {'get_default_behavior': {'double_click': {'mode': 'unknown'},
'err_code': 0,
'long_press': {'mode': 'unknown'}},
'get_dimmer_parameters': {'bulb_type': 1,
'err_code': 0,
'fadeOffTime': 0,
'fadeOnTime': 0,
'gentleOffTime': 10000,
'gentleOnTime': 3000,
'maxThreshold': 100,
'minThreshold': 14,
'rampRate': 30}},
'system': {'get_sysinfo': {'active_mode': 'none',
'alias': '#MASKED_NAME#',
'brightness': 52,
'dev_name': 'Wi-Fi Smart Dimmer with sensor',
'deviceId': 'REDACTED_3547B7DCA5429DC92708C762180D397',
'err_code': 0,
'feature': 'TIM',
'hold_on': 1,
'hwId': 'REDACTED_F31AF2EA08CD6610E699746',
'hw_ver': '1.0',
'icon_hash': '',
'latitude_i': 0,
'led_off': 0,
'lnk_on': 1,
'longitude_i': 0,
'mac': '40:ED:00:00:00:00',
'max_thr': 1,
'mic_type': 'IOT.SMARTPLUGSWITCH',
'model': 'ES20M(US)',
'next_action': {'type': -1},
'obd_src': 'tplink',
'oemId': 'REDACTED_BABAA04B18C0033D0368A92',
'on_time': 0,
'preferred_state': [{'brightness': 97, 'index': 0},
{'brightness': 75, 'index': 1},
{'brightness': 50, 'index': 2},
{'brightness': 25, 'index': 3}],
'relay_state': 0,
'rssi': -50,
'status': 'new',
'sw_ver': '1.1.5 Build 241206 Rel.142818',
'updating': 0}},
'time': {'get_time': {'err_code': 0,
'hour': 16,
'mday': 25,
'min': 59,
'month': 2,
'sec': 47,
'year': 2025},
'get_timezone': {'err_code': 0, 'index': 18}}}
DEBUG Initial update, so consider supported: light iotmodule.py:73
== Main Closet - ES20M ==
Host: 192.168.100.125
Port: 9999
Device state: False
Time: 2025-02-25 16:59:47-05:00 (tz: EST)
Hardware: 1.0 (US)
Firmware: 1.1.5 Build 241206 Rel.142818
MAC (rssi): 40:ED:00:6D:80:EF (-50)

== Primary features ==
State (state): False
PIR Triggered (pir_triggered): False
Ambient Light (ambient_light): 5 %
Brightness (brightness): 52 (range: 0-100)

== Information ==
On since (on_since): None
Cloud connection (cloud_connection): True
PIR Value (pir_value): -49

== Configuration ==
LED (led): True
PIR enabled (pir_enabled): True
Motion Sensor Range (pir_range): Far Mid Near Custom
Motion Sensor Threshold (pir_threshold): 50 (range: 0-100)
Ambient light enabled (ambient_light_enabled): True
Minimum dimming level (dimmer_threshold_min): 14 (range: 0-51)
Dimmer fade off time (dimmer_fade_off_time): 0:00:00 (range: 0-10000)
Dimmer fade on time (dimmer_fade_on_time): 0:00:00 (range: 0-10000)
Dimmer gentle off time (dimmer_gentle_off_time): 0:00:10 (range: 0-120000)
Dimmer gentle on time (dimmer_gentle_on_time): 0:00:03 (range: 0-120000)
Dimmer ramp rate (dimmer_ramp_rate): 30 (range: 10-50)

== Debug ==
RSSI (rssi): -50 dBm
Reboot (reboot):
PIR ADC Value (pir_adc_value): 2096
PIR ADC Min (pir_adc_min): 0
PIR ADC Mid (pir_adc_mid): 2047
PIR ADC Max (pir_adc_max): 4095
PIR Percentile (pir_percent): -2.3937469467513433 %

DEBUG Adding query for <Module Schedule (schedule) for 192.168.100.125>: {'schedule': {'get_rules': {}, 'get_next_action': {}}} iotdevice.py:411
DEBUG Adding query for <Module Usage (schedule) for 192.168.100.125>: {'schedule': {'get_realtime': {}, 'get_daystat': {'year': 2025, iotdevice.py:411
'month': 2}, 'get_monthstat': {'year': 2025}}}
DEBUG Adding query for <Module Antitheft (anti_theft) for 192.168.100.125>: {'anti_theft': {'get_rules': {}, 'get_next_action': {}}} iotdevice.py:411
DEBUG Adding query for <Module Time (time) for 192.168.100.125>: {'time': {'get_time': {}, 'get_timezone': {}}} iotdevice.py:411
DEBUG Adding query for <Module Cloud (cnCloud) for 192.168.100.125>: {'cnCloud': {'get_info': {}}} iotdevice.py:411
DEBUG Adding query for <Module Led (system) for 192.168.100.125>: {} iotdevice.py:411
DEBUG Adding query for <Module Motion (smartlife.iot.PIR) for 192.168.100.125>: {'smartlife.iot.PIR': {'get_config': {}, iotdevice.py:411
'get_adc_value': {}}}
DEBUG Adding query for <Module AmbientLight (smartlife.iot.LAS) for 192.168.100.125>: {'smartlife.iot.LAS': {'get_config': {}, iotdevice.py:411
'get_current_brt': {}}}
DEBUG Adding query for <Module Dimmer (smartlife.iot.dimmer) for 192.168.100.125>: {'smartlife.iot.dimmer': {'get_dimmer_parameters': iotdevice.py:411
{}, 'get_default_behavior': {}}}
DEBUG Initial update, so consider supported: light iotmodule.py:73
DEBUG Adding query for <Module Light (light) for 192.168.100.125>: {} iotdevice.py:411
DEBUG 192.168.100.125 >> iotprotocol.py:143
{"system":{"get_sysinfo":{}},"schedule":{"get_rules":{},"get_next_action":{},"get_realtime":{},"get_daystat":{"year":2025,"month
":2},"get_monthstat":{"year":2025}},"anti_theft":{"get_rules":{},"get_next_action":{}},"time":{"get_time":{},"get_timezone":{}},
"cnCloud":{"get_info":{}},"smartlife.iot.PIR":{"get_config":{},"get_adc_value":{}},"smartlife.iot.LAS":{"get_config":{},"get_cur
rent_brt":{}},"smartlife.iot.dimmer":{"get_dimmer_parameters":{},"get_default_behavior":{}}}
DEBUG Device 192.168.100.125 sending query xortransport.py:79
{"system":{"get_sysinfo":{}},"schedule":{"get_rules":{},"get_next_action":{},"get_realtime":{},"get_daystat":{"year":2025,"month
":2},"get_monthstat":{"year":2025}},"anti_theft":{"get_rules":{},"get_next_action":{}},"time":{"get_time":{},"get_timezone":{}},
"cnCloud":{"get_info":{}},"smartlife.iot.PIR":{"get_config":{},"get_adc_value":{}},"smartlife.iot.LAS":{"get_config":{},"get_cur
rent_brt":{}},"smartlife.iot.dimmer":{"get_dimmer_parameters":{},"get_default_behavior":{}}}
DEBUG Device 192.168.100.125 query response received xortransport.py:91
DEBUG 192.168.100.125 << {'anti_theft': {'get_next_action': {'err_code': -2, iotprotocol.py:152
'err_msg': 'member not support'},
'get_rules': {'enable': 0,
'err_code': 0,
'rule_list': [],
'version': 2}},
'cnCloud': {'get_info': {'binded': 1,
'cld_connection': 1,
'err_code': 0,
'fwDlPage': '',
'fwNotifyType': -1,
'illegalType': 0,
'server': 'n-devs.tplinkcloud.com',
'stopConnect': 0,
'tcspInfo': '',
'tcspStatus': 1,
'username': 'user@example.com'}},
'schedule': {'get_daystat': {'day_list': [{'day': 1,
'month': 2,
'time': 107,
'year': 2025},
{'day': 2,
'month': 2,
'time': 72,
'year': 2025},
{'day': 3,
'month': 2,
'time': 100,
'year': 2025},
{'day': 4,
'month': 2,
'time': 117,
'year': 2025},
{'day': 5,
'month': 2,
'time': 130,
'year': 2025},
{'day': 6,
'month': 2,
'time': 190,
'year': 2025},
{'day': 7,
'month': 2,
'time': 113,
'year': 2025},
{'day': 8,
'month': 2,
'time': 122,
'year': 2025},
{'day': 9,
'month': 2,
'time': 163,
'year': 2025},
{'day': 10,
'month': 2,
'time': 99,
'year': 2025},
{'day': 11,
'month': 2,
'time': 104,
'year': 2025},
{'day': 12,
'month': 2,
'time': 872,
'year': 2025},
{'day': 13,
'month': 2,
'time': 126,
'year': 2025},
{'day': 14,
'month': 2,
'time': 91,
'year': 2025},
{'day': 15,
'month': 2,
'time': 103,
'year': 2025},
{'day': 16,
'month': 2,
'time': 119,
'year': 2025},
{'day': 17,
'month': 2,
'time': 365,
'year': 2025},
{'day': 18,
'month': 2,
'time': 83,
'year': 2025},
{'day': 19,
'month': 2,
'time': 137,
'year': 2025},
{'day': 20,
'month': 2,
'time': 249,
'year': 2025},
{'day': 21,
'month': 2,
'time': 479,
'year': 2025},
{'day': 22,
'month': 2,
'time': 10,
'year': 2025},
{'day': 23,
'month': 2,
'time': 93,
'year': 2025},
{'day': 24,
'month': 2,
'time': 137,
'year': 2025},
{'day': 25,
'month': 2,
'time': 67,
'year': 2025}],
'err_code': 0},
'get_monthstat': {'err_code': 0,
'month_list': [{'month': 1,
'time': 3763,
'year': 2025},
{'month': 2,
'time': 4248,
'year': 2025}]},
'get_next_action': {'err_code': 0, 'type': -1},
'get_realtime': {'err_code': -2, 'err_msg': 'member not support'},
'get_rules': {'enable': 1,
'err_code': 0,
'rule_list': [],
'version': 2}},
'smartlife.iot.LAS': {'get_config': {'devs': [{'dark_index': 0,
'enable': 1,
'hw_id': 0,
'level_array': [{'adc': 390,
'name': 'cloudy',
'value': 16},
{'adc': 300,
'name': 'overcast',
'value': 12},
{'adc': 222,
'name': 'dawn',
'value': 9},
{'adc': 222,
'name': 'twilight',
'value': 9},
{'adc': 111,
'name': 'total '
'darkness',
'value': 5},
{'adc': 2400,
'name': 'custom',
'value': 95}],
'max_adc': 2550,
'min_adc': 0}],
'err_code': 0,
'ver': '1.0'},
'get_current_brt': {'err_code': 0, 'value': 5}},
'smartlife.iot.PIR': {'get_adc_value': {'err_code': 0, 'value': 2011},
'get_config': {'array': [80, 50, 20, 0],
'cold_time': 600000,
'enable': 1,
'err_code': 0,
'max_adc': 4095,
'min_adc': 0,
'trigger_index': 1,
'version': '1.0'}},
'smartlife.iot.dimmer': {'get_default_behavior': {'double_click': {'mode': 'unknown'},
'err_code': 0,
'long_press': {'mode': 'unknown'}},
'get_dimmer_parameters': {'bulb_type': 1,
'err_code': 0,
'fadeOffTime': 0,
'fadeOnTime': 0,
'gentleOffTime': 10000,
'gentleOnTime': 3000,
'maxThreshold': 100,
'minThreshold': 14,
'rampRate': 30}},
'system': {'get_sysinfo': {'active_mode': 'none',
'alias': '#MASKED_NAME#',
'brightness': 52,
'dev_name': 'Wi-Fi Smart Dimmer with sensor',
'deviceId': 'REDACTED_3547B7DCA5429DC92708C762180D397',
'err_code': 0,
'feature': 'TIM',
'hold_on': 1,
'hwId': 'REDACTED_F31AF2EA08CD6610E699746',
'hw_ver': '1.0',
'icon_hash': '',
'latitude_i': 0,
'led_off': 0,
'lnk_on': 1,
'longitude_i': 0,
'mac': '40:ED:00:00:00:00',
'max_thr': 1,
'mic_type': 'IOT.SMARTPLUGSWITCH',
'model': 'ES20M(US)',
'next_action': {'type': -1},
'obd_src': 'tplink',
'oemId': 'REDACTED_BABAA04B18C0033D0368A92',
'on_time': 0,
'preferred_state': [{'brightness': 97, 'index': 0},
{'brightness': 75, 'index': 1},
{'brightness': 50, 'index': 2},
{'brightness': 25, 'index': 3}],
'relay_state': 0,
'rssi': -50,
'status': 'new',
'sw_ver': '1.1.5 Build 241206 Rel.142818',
'updating': 0}},
'time': {'get_time': {'err_code': 0,
'hour': 16,
'mday': 25,
'min': 59,
'month': 2,
'sec': 48,
'year': 2025},
'get_timezone': {'err_code': 0, 'index': 18}}}

@Thireus
Copy link

Thireus commented Feb 26, 2025

@barney34, I would suggest to try connect the device to a different network to troubleshoot if this is an issue with your network.

As for the authentication issue that some P110M devices are facing:

  1. There is no special treatment for the KLAP protocol implementation on the Android and iOS app or for how "tss" devices should treat hashing and/or passcode/username and other secrets for handshake1. Which suggests to me that either the mobile client code has not been updated (because deprecated for the P110M devices) or maybe that there is nothing wrong on the client side... which suggests a firmware incompatibility for some P110M devices for the computation of the KLAP hashes or parsing of the credentials and seed.
  2. After inspecting the S/N number of the devices I own, the 224A* and 224B* devices are reported as "tss" and the 224C* device is reported as tp-link. This suggests to me that the 224C* devices may be newer and there could be an incompatibility with the latest firmware on the 224A* and 224B* devices for how KLAP is handled, specifically the hashing operations or parsing the seed/credentials. <-- This has nothing to do with S/N, this is simply because the first device I've plugged in used the tp-link app for onboarding and the following devices used tss! DO NOT USE TSS, only plug in one device at a time to onboard them!
  3. I was not able to obtain a copy of the latest Tapo P110M firmware, and at the moment I am also not able to identify where KLAP handshake1 is implemented on the older firmware (https://github.com/tapo-firmware/Directory/blob/main/all_keys.txt). But I believe that doing a static analysis of the firmware may not reveal any defect about how the hashes are computed... There is a chance it could be a memory management bug on the specific 224A* and 224B* devices for these handshake1 hashing operations or that there is some form of special treatment of the credentials on the tss vs tp-link OBS devices before they are processed. <-- This! Don't use tss!

At this stage this is all speculations... If this is a defect, then they can easily correct it with a firmware update. If this is intentional, until we get a copy of the firmware or they update the same on the iOS/Android apps we will have quite a hard time figuring out which special operations are performed for handshake1...

I have ordered a bunch of P110M and will assess if the theory holds - that the 224C* devices and above are obs_src "tp-link" and not affected. I'll update the ticket.

@Thireus
Copy link

Thireus commented Feb 26, 2025

Alright, I've figured it out! I had a good intuition initially that it had something to do with tss, the solution only became clear once I found what TSS was about...

tss = "TP-Link Simple Setup" - https://community.tp-link.com/en/home/forum/topic/692206

tss is an Oboarding protocol which allows new devices that you plug in to obtain the configuration (credentials and WiFi password) from existing devices on your network.

If you plug in a new device and there are existing Tapo devices on your network that also support tss, then this is the onboarding protocol that will be used, and it would appear that it completely breaks local KLAP. I suppose this might be because the credentials are shared with additional hashing or in a different format when the device uses tss onboarding. So there is indeed a different treatment when tss is used to onboard new devices.

Here is how you can fix the KLAP auth issue on your devices:

  1. Physically remove all existing similar TP-Link devices from your house power sockets.
  2. Plug in only ONE device
  3. Press the power button for 8 seconds (until you see the LED flashing green) - this will factory reset the device
  4. Open the Tapo app on your smartphone and make sure your smartphone bluetooth is on
  5. Find the device model to add, and wait for it to be found (make sure you are close to the device)
  6. Add the device... This will effectively do a "tp-link" onboarding as opposed to a "tss" onboarding
  7. Once the device is added, auth should now work. And --debug should read 'obd_src': 'tp-link' (if not it means you have other TP link devices that support tss plugged in somewhere in your house).
  8. Repeat the procedure for all your devices.

Hope this helps! All my P110M are now working as expected with Home Assistant and they are all on the latest firmware.

Edit: it's still unclear if TP-Link broke KLAP with TSS in the latest firmware because of a new hashing mechanism when TSS has been used at onboarding OR if there is a bug with their firmware... Time will tell I suppose. In the meantime this issue should probably remain opened, because if there is a different hashing mechanism when TSS is used it means it could be ported to python-kasa after reverse engineering it.

Thireus added a commit to Thireus/home-assistant.io that referenced this issue Feb 26, 2025
TSS breaks local authentication for new firmware - See python-kasa/python-kasa#1353 (comment)
frenck pushed a commit to home-assistant/home-assistant.io that referenced this issue Feb 28, 2025
TSS breaks local authentication for new firmware - See python-kasa/python-kasa#1353 (comment)
@exxamalte
Copy link

With a few more manual steps inside the Tapo app I was able to add a new P110M device without unplugging all my existing devices:

  1. Press the power button for 8 seconds (until you see the LED flashing green) - this will factory reset the device
  2. Open the Tapo app on your smartphone and make sure your smartphone bluetooth is on
  3. Do not click on anything in the app showing you a newly detected device. Instead, click the + button at the top right, then "Add Device", then choose the model like for example "Plugs" and "Tapo P110M", and then follow the process including selecting your WiFi network, naming the device etc.
  4. After following this procedure, auth now works. And --debug reads 'obd_src': 'tp-link'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants