From 595bb94fa2a345bc29f5183c9ab6b033ca1002d7 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 31 Jul 2026 05:00:46 -0400 Subject: [PATCH] Automatically detect device class in vizio config flow (#177642) Co-authored-by: Claude Fable 5 --- homeassistant/components/vizio/config_flow.py | 31 ++++++++++--------- homeassistant/components/vizio/const.py | 1 - homeassistant/components/vizio/strings.json | 1 - tests/components/vizio/conftest.py | 10 ++++++ tests/components/vizio/test_config_flow.py | 6 ++-- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index bf05be04a6f8..bb256ac0ffff 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -35,7 +35,6 @@ from .const import ( CONF_APPS_TO_INCLUDE_OR_EXCLUDE, CONF_INCLUDE_OR_EXCLUDE, CONF_VOLUME_STEP, - DEFAULT_DEVICE_CLASS, DEFAULT_NAME, DEFAULT_VOLUME_STEP, DEVICE_ID, @@ -64,14 +63,6 @@ def _get_config_schema(input_dict: dict[str, Any] | None = None) -> vol.Schema: CONF_NAME, default=input_dict.get(CONF_NAME, DEFAULT_NAME) ): str, vol.Required(CONF_HOST, default=input_dict.get(CONF_HOST)): str, - vol.Required( - CONF_DEVICE_CLASS, - default=input_dict.get(CONF_DEVICE_CLASS, DEFAULT_DEVICE_CLASS), - ): vol.All( - str, - vol.Lower, - vol.In([MediaPlayerDeviceClass.TV, MediaPlayerDeviceClass.SPEAKER]), - ), vol.Optional( CONF_ACCESS_TOKEN, default=input_dict.get(CONF_ACCESS_TOKEN, "") ): str, @@ -109,6 +100,17 @@ def _get_device( ) +async def _async_detect_device_class( + hass: HomeAssistant, host: str +) -> MediaPlayerDeviceClass: + """Detect whether the device at host is a TV or a speaker.""" + return ( + MediaPlayerDeviceClass.TV + if await async_is_tv(host, session=async_get_clientsession(hass, False)) + else MediaPlayerDeviceClass.SPEAKER + ) + + async def _async_get_unique_id( hass: HomeAssistant, host: str, device_class: str ) -> str | None: @@ -248,6 +250,11 @@ class VizioConfigFlow(ConfigFlow, domain=DOMAIN): if user_input is not None: # Store current values in case setup fails and user needs to edit self._user_schema = _get_config_schema(user_input) + # Zeroconf discovery provides the device class; detect it otherwise + if CONF_DEVICE_CLASS not in user_input: + user_input[CONF_DEVICE_CLASS] = await _async_detect_device_class( + self.hass, user_input[CONF_HOST] + ) if self.unique_id is None: unique_id = await _async_get_unique_id( self.hass, user_input[CONF_HOST], user_input[CONF_DEVICE_CLASS] @@ -308,11 +315,7 @@ class VizioConfigFlow(ConfigFlow, domain=DOMAIN): num_chars_to_strip = len(discovery_info.type) + 1 name = discovery_info.name[:-num_chars_to_strip] - device_class = ( - MediaPlayerDeviceClass.TV - if await async_is_tv(host) - else MediaPlayerDeviceClass.SPEAKER - ) + device_class = await _async_detect_device_class(self.hass, host) # Set unique ID early for discovery flow so we can abort if needed unique_id = await _async_get_unique_id(self.hass, host, device_class) diff --git a/homeassistant/components/vizio/const.py b/homeassistant/components/vizio/const.py index 101d6e6d9195..06838a8a04a9 100644 --- a/homeassistant/components/vizio/const.py +++ b/homeassistant/components/vizio/const.py @@ -17,7 +17,6 @@ CONF_NAME_SPACE = "NAME_SPACE" CONF_MESSAGE = "MESSAGE" CONF_VOLUME_STEP = "volume_step" -DEFAULT_DEVICE_CLASS = MediaPlayerDeviceClass.TV DEFAULT_NAME = "Vizio SmartCast" DEFAULT_TIMEOUT = 8 DEFAULT_VOLUME_STEP = 1 diff --git a/homeassistant/components/vizio/strings.json b/homeassistant/components/vizio/strings.json index 585123809a67..05f6403aef64 100644 --- a/homeassistant/components/vizio/strings.json +++ b/homeassistant/components/vizio/strings.json @@ -29,7 +29,6 @@ "user": { "data": { "access_token": "[%key:common::config_flow::data::access_token%]", - "device_class": "Device type", "host": "[%key:common::config_flow::data::host%]", "name": "[%key:common::config_flow::data::name%]" }, diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index d783ed922e5f..56fd48c43f0c 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -238,6 +238,16 @@ def vizio_guess_device_type_fixture() -> Generator[None]: yield +@pytest.fixture(name="vizio_detect_tv") +def vizio_detect_tv_fixture() -> Generator[None]: + """Mock vizio device type probe to report a TV.""" + with patch( + "homeassistant.components.vizio.config_flow.async_is_tv", + return_value=True, + ): + yield + + @pytest.fixture(name="vizio_cant_connect") def vizio_cant_connect_fixture() -> Generator[None]: """Mock vizio device can't connect with valid auth.""" diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index 2201aeb5d6e9..6c147513bd33 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -45,7 +45,9 @@ from .const import ( from tests.common import MockConfigEntry -@pytest.mark.usefixtures("vizio_connect", "vizio_bypass_setup") +@pytest.mark.usefixtures( + "vizio_connect", "vizio_bypass_setup", "vizio_guess_device_type" +) async def test_user_flow_minimum_fields(hass: HomeAssistant) -> None: """Test user config flow with minimum fields.""" # test form shows @@ -66,7 +68,7 @@ async def test_user_flow_minimum_fields(hass: HomeAssistant) -> None: assert result["data"][CONF_DEVICE_CLASS] == MediaPlayerDeviceClass.SPEAKER -@pytest.mark.usefixtures("vizio_connect", "vizio_bypass_setup") +@pytest.mark.usefixtures("vizio_connect", "vizio_bypass_setup", "vizio_detect_tv") async def test_user_flow_all_fields(hass: HomeAssistant) -> None: """Test user config flow with all fields.""" # test form shows