diff --git a/kasa/cli/discover.py b/kasa/cli/discover.py index af367e32b..4ee01d3ee 100644 --- a/kasa/cli/discover.py +++ b/kasa/cli/discover.py @@ -29,6 +29,7 @@ from kasa.protocols.iotprotocol import REDACTORS as IOT_REDACTORS from kasa.protocols.protocol import redact_data +from ..exceptions import UnsupportedAuthenticationError from ..json import dumps as json_dumps from .common import echo, error @@ -70,14 +71,16 @@ async def detail(ctx: click.Context) -> DeviceDict: async def print_unsupported(unsupported_exception: UnsupportedDeviceError) -> None: unsupported.append(unsupported_exception) async with sem: + echo("== Unsupported device ==") + echo(f"\t{unsupported_exception}") + echo() + if unsupported_exception.discovery_result: - echo("== Unsupported device ==") _echo_discovery_info(unsupported_exception.discovery_result) echo() - else: - echo("== Unsupported device ==") - echo(f"\t{unsupported_exception}") - echo() + if isinstance(unsupported_exception, UnsupportedAuthenticationError): + echo("\t[red bold]Provisioned using unsupported 'tss'.[/red bold]") + echo("\tTo fix, reset and provision manually.") from .device import state diff --git a/kasa/discover.py b/kasa/discover.py index 8e2b981af..22aaa1afb 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -123,6 +123,7 @@ from kasa.exceptions import ( KasaException, TimeoutError, + UnsupportedAuthenticationError, UnsupportedDeviceError, ) from kasa.iot.iotdevice import IotDevice, _extract_sys_info @@ -805,6 +806,13 @@ def _get_connection_parameters( discovery_result: DiscoveryResult, ) -> DeviceConnectionParameters: """Get connection parameters from the discovery result.""" + # Raise specific exception for unsupported authentication source + if getattr(discovery_result, "obd_src", None) == "tss": + raise UnsupportedAuthenticationError( + f"Device at {discovery_result.ip} uses unsupported onboarding 'tss'.", + discovery_result=discovery_result.to_dict(), + host=discovery_result.ip, + ) type_ = discovery_result.device_type if (encrypt_schm := discovery_result.mgt_encrypt_schm) is None: raise UnsupportedDeviceError( @@ -885,7 +893,7 @@ def _get_device_instance( conn_params = Discover._get_connection_parameters(discovery_result) config.connection_type = conn_params except KasaException as ex: - if isinstance(ex, UnsupportedDeviceError): + if isinstance(ex, UnsupportedDeviceError | UnsupportedAuthenticationError): raise raise UnsupportedDeviceError( f"Unsupported device {config.host} of type {type_} " diff --git a/kasa/exceptions.py b/kasa/exceptions.py index 1c764ad7a..5115dabce 100644 --- a/kasa/exceptions.py +++ b/kasa/exceptions.py @@ -200,3 +200,11 @@ def from_int(value: int) -> SmartErrorCode: SmartErrorCode.TRANSPORT_UNKNOWN_CREDENTIALS_ERROR, SmartErrorCode.HOMEKIT_LOGIN_FAIL, ] + + +class UnsupportedAuthenticationError(UnsupportedDeviceError, AuthenticationError): + """Raised when authentication fails with unsupported provisioning method. + + This can be used to display a more helpful message to the user when their devices + have been provisioned using the tplink simple setup (tss). + """ diff --git a/tests/discovery_fixtures.py b/tests/discovery_fixtures.py index 3cf726f48..939709048 100644 --- a/tests/discovery_fixtures.py +++ b/tests/discovery_fixtures.py @@ -399,3 +399,21 @@ async def mock_discover(self): mocker.patch("kasa.discover._DiscoverProtocol.do_discover", mock_discover) return discovery_data + + +def test_unsupported_authentication_exception_for_tss(): + from kasa.discover import Discover, DiscoveryResult + from kasa.exceptions import UnsupportedAuthenticationError + + # Create a mock discovery result with obd_src='tss' + dr = DiscoveryResult( + device_type="SomeType", + device_model="SomeModel", + device_id="SomeID", + ip="127.0.0.2", + mac="00:11:22:33:44:55", + obd_src="tss", + ) + with pytest.raises(UnsupportedAuthenticationError) as excinfo: + Discover._get_connection_parameters(dr) + assert "obd_src='tss'" in str(excinfo.value)