Skip to content

Refactor discovery and connection routing#1725

Draft
ZeliardM wants to merge 1 commit into
python-kasa:masterfrom
ZeliardM:feature/discover
Draft

Refactor discovery and connection routing#1725
ZeliardM wants to merge 1 commit into
python-kasa:masterfrom
ZeliardM:feature/discover

Conversation

@ZeliardM

Copy link
Copy Markdown
Collaborator

Summary

This PR consolidates discovery, device creation, and connection routing into a single flow.

Discovery responses are decoded and normalized by their respective endpoints, then passed through shared logic that:

  • chooses the authoritative response for each host;
  • maps advertised device families to concrete device classes; and
  • selects the appropriate protocol and transport.

Previously, parts of this logic were duplicated across discovery, direct connections, the CLI, and devtools. Centralizing it should make it easier to add new discovery endpoints, device families, and connection types without updating several separate selection paths.

This PR also supersedes:

Discovery

Discovery supports three endpoints:

  • UDP on port 9999;
  • TDP on port 20002; and
  • TDP on port 20004.

The two TDP ports are handled independently because they may serve different device populations.

UDP responses are held until discovery finishes. TDP responses are processed immediately and take precedence over UDP responses from the same IP. Once a host responds through TDP, any stored or later UDP response for that host is ignored.

TDP remains authoritative even when processing its response results in an authentication error, unsupported-device result, or invalid-response error. This prevents discovery from falling back to a less useful UDP response for the same device.

Each endpoint handles its own decoding and then produces a common discovery candidate. The shared processing path also:

  • normalizes source addresses;
  • rejects malformed JSON structures;
  • deduplicates repeated responses from the same endpoint;
  • prevents raw callbacks from mutating internal discovery state; and
  • propagates callback and processing failures correctly.

Raw-response callbacks now include the discovery source and port, allowing callers to distinguish UDP responses from either TDP endpoint.

Device and connection routing

Device-family, protocol, transport, and concrete-class selection now live in device_factory.

Advertised device families are kept separate from connection routes. After discovery or direct connection input has been normalized, the same route definitions are used by:

  • discovery;
  • Device.connect();
  • protocol construction; and
  • Discover.try_connect_all().

Existing special cases for cameras, doorbells, hubs, robot vacuums, HTTPS, fixed-encryption devices, and the IOT, SMART, and SMARTCAM protocol families are preserved.

Direct-connection attempts are also generated from these route definitions instead of using a separate matrix of possible family and encryption combinations.

IOT family names such as IOT.SMARTPLUGSWITCH and IOT.SMARTBULB describe broad advertised families rather than concrete Python classes. The factory determines the actual class from get_sysinfo, regardless of whether that information came from UDP discovery or was queried using a protocol selected from a TDP response.

This keeps class selection correct for plugs, power strips, wall switches, dimmers, bulbs, and light strips across both discovery methods.

Unknown IOT device types now raise UnsupportedDeviceError instead of exposing a lookup error or falling back to a plug.

Connection parameters and versioned KLAP

DeviceConnectionParameters now contains the full connection identity used for discovery, direct connections, serialization, and route selection.

The TDP new_klap value is exposed as the optional integer field klap_version.

For IOT KLAP devices:

  • no klap_version uses the original KlapTransport;
  • a positive klap_version uses KlapTransportV2; and
  • login_version remains separate and does not select the IOT KLAP handshake.

SMART KLAP devices continue to use their existing KLAP V2 route.

The CLI and fixture devtool expose this setting as -kv / --klap-version, alongside device family, encryption type, login version, and HTTPS options.

Discovery outcomes and errors

Discovery distinguishes between:

  • supported devices;
  • unsupported devices;
  • authentication failures that prevent initialization or updates; and
  • authentication failures caused by unsupported onboarding methods.

DiscoveryAuthenticationError adds the host and discovery response to authentication failures raised during discovery.

UnsupportedAuthenticationError inherits from both AuthenticationError and UnsupportedDeviceError. A device is only classified this way after an authentication attempt actually fails. Discovery metadata by itself is not enough to classify missing or incorrect credentials as unsupported onboarding.

The discovery API has separate callbacks for unsupported devices and authentication failures. These callbacks use the same task and exception handling as successful-device callbacks.

Protocols owned by discovery are also closed when initialization or callback processing fails, or when discovery is interrupted.

The new exceptions are exported from the package and included in the API reference.

CLI

The discovery CLI now consumes the same normalized discovery and connection data as the library.

The default discovery command updates supported devices before displaying them and reports a unique total across successful, unsupported, and authentication-blocked devices.

discover list uses one row format for all outcomes. Each row may include:

  • host;
  • model;
  • device family;
  • encryption type;
  • HTTPS;
  • login version;
  • KLAP version;
  • discovery source and port; and
  • alias or update result.

The format is shared across successful updates, timeouts, authentication failures, unsupported devices, unsupported authentication, and other update errors.

Targeted hostname discovery also uses the resolved response identity, avoiding duplicate rows for the hostname and its IP address.

discover raw includes the source port and applies the appropriate UDP or TDP redactor.

discover config first attempts targeted discovery and reports the route from the authoritative response. It only falls back to direct probing when discovery does not return usable connection information. Direct-probing output includes the complete route being attempted.

The remaining connection options have been made more predictable:

  • common IOT types can still be selected with --type;
  • advanced direct connections require both --device-family and --encrypt-type;
  • login and KLAP versions are independent valued options;
  • invalid option combinations are rejected rather than ignored;
  • direct-connection options require --host and cannot be passed to discover; and
  • the configurable UDP port cannot replace either reserved TDP port.

Credential hashes are preserved through discovery, direct probing, configuration output, and devtools.

Devtools

dump_devinfo now uses the same response-selection and direct-connection logic as the library and CLI.

It supports the same device-family, encryption, login-version, KLAP-version, HTTPS, credential-hash, timeout, and port options.

It also reports authentication and unsupported-device outcomes and preserves the authoritative raw discovery response when generating fixtures.

Redaction and discovery-formatting helpers used across modules have been renamed from private helpers to shared public helpers.

Documentation and fixtures

The user and developer documentation has been updated for the revised discovery and connection flow.

It covers:

  • UDP and TDP discovery;
  • TDP precedence over UDP;
  • the separate 20002 and 20004 endpoints;
  • direct-connection parameters and CLI options;
  • independent login and KLAP versions;
  • supported, unsupported, and authentication-blocked results;
  • credential hashes;
  • protocol and transport families;
  • directed broadcast behavior on systems with multiple interfaces; and
  • the corresponding devtool options.

The exception reference includes the new discovery authentication errors.

An authenticated IOT KLAP HS300 fixture and a serialized versioned-KLAP DeviceConfig fixture cover the new route and update the generated supported-device documentation.

API notes

Discover.try_connect_all() remains the public entry point for direct probing, although its implementation has moved into the device factory.

ConnectAttempt now includes the complete connection_type, so callbacks can determine the exact route that was attempted. Code that tuple-unpacked the previous four fields will need to use the updated structure.

Because klap_version is optional, existing serialized DeviceConfig data remains compatible.

Validation

The relevant discovery, device-factory, device-config, CLI, and devtool tests pass.

The changed Python files also pass Ruff and mypy, and the documentation builds successfully with warnings treated as errors.

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.98279% with 178 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.24%. Comparing base (aba457a) to head (209b372).

Files with missing lines Patch % Lines
kasa/discover.py 84.29% 47 Missing and 29 partials ⚠️
kasa/cli/discover.py 78.89% 23 Missing and 19 partials ⚠️
kasa/device_factory.py 84.75% 24 Missing and 10 partials ⚠️
kasa/iot/iotdevice.py 78.00% 5 Missing and 6 partials ⚠️
kasa/cli/main.py 83.01% 6 Missing and 3 partials ⚠️
kasa/device.py 73.33% 2 Missing and 2 partials ⚠️
kasa/cli/device.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1725      +/-   ##
==========================================
- Coverage   93.29%   92.24%   -1.05%     
==========================================
  Files         157      157              
  Lines        9932    10657     +725     
  Branches     1022     1182     +160     
==========================================
+ Hits         9266     9831     +565     
- Misses        471      569      +98     
- Partials      195      257      +62     

☔ 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.

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

Successfully merging this pull request may close these issues.

1 participant