diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 6fe1c28a2ef..9076d5b7338 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -26,6 +26,12 @@ HOST_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str}) HTTP_CONNECT_ERRORS = (asyncio.TimeoutError, aiohttp.ClientError) +def _remove_prefix(shelly_str): + if shelly_str.startswith("shellyswitch"): + return shelly_str[6:] + return shelly_str + + async def validate_input(hass: core.HomeAssistant, host, data): """Validate the user input allows us to connect. @@ -45,7 +51,10 @@ async def validate_input(hass: core.HomeAssistant, host, data): await coap_context.shutdown() # Return info that you want to store in the config entry. - return {"title": device.settings["name"], "mac": device.settings["device"]["mac"]} + return { + "title": device.settings["name"], + "hostname": device.settings["device"]["hostname"], + } class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -86,7 +95,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" else: return self.async_create_entry( - title=device_info["title"] or self.host, + title=device_info["title"] or device_info["hostname"], data=user_input, ) @@ -112,7 +121,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" else: return self.async_create_entry( - title=device_info["title"] or self.host, + title=device_info["title"] or device_info["hostname"], data={**user_input, CONF_HOST: self.host}, ) else: @@ -145,7 +154,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context["title_placeholders"] = {"name": zeroconf_info["host"]} + self.context["title_placeholders"] = { + "name": _remove_prefix(zeroconf_info["properties"]["id"]) + } return await self.async_step_confirm_discovery() async def async_step_confirm_discovery(self, user_input=None): @@ -164,7 +175,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" else: return self.async_create_entry( - title=device_info["title"] or self.host, data={"host": self.host} + title=device_info["title"] or device_info["hostname"], + data={"host": self.host}, ) return self.async_show_form( diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 69a09204227..0efceba56a5 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -1,7 +1,7 @@ { "title": "Shelly", "config": { - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "user": { "description": "Before set up, the battery-powered device must be woken up by pressing the button on the device.", diff --git a/homeassistant/components/shelly/translations/en.json b/homeassistant/components/shelly/translations/en.json index 2727cfbceeb..e109a9ac6f3 100644 --- a/homeassistant/components/shelly/translations/en.json +++ b/homeassistant/components/shelly/translations/en.json @@ -10,7 +10,7 @@ "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Do you want to set up the {model} at {host}?\n\nBefore set up, the battery-powered device must be woken up by pressing the button on the device." diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 03eb907d09b..280688a0618 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -11,6 +11,21 @@ from homeassistant.components.shelly.const import DOMAIN from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry +MOCK_SETTINGS = { + "name": "Test name", + "device": {"mac": "test-mac", "hostname": "test-host"}, +} +DISCOVERY_INFO = { + "host": "1.1.1.1", + "name": "shelly1pm-12345", + "properties": {"id": "shelly1pm-12345"}, +} +SWITCH25_DISCOVERY_INFO = { + "host": "1.1.1.1", + "name": "shellyswitch25-12345", + "properties": {"id": "shellyswitch25-12345"}, +} + async def test_form(hass): """Test we get the form.""" @@ -29,7 +44,7 @@ async def test_form(hass): new=AsyncMock( return_value=Mock( shutdown=AsyncMock(), - settings={"name": "Test name", "device": {"mac": "test-mac"}}, + settings=MOCK_SETTINGS, ) ), ), patch( @@ -53,6 +68,51 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_title_without_name_and_prefix(hass): + """Test we set the title to the hostname when the device doesn't have a name.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + settings = MOCK_SETTINGS.copy() + settings["name"] = None + settings["device"] = settings["device"].copy() + settings["device"]["hostname"] = "shelly1pm-12345" + with patch( + "aioshelly.get_info", + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + ), patch( + "aioshelly.Device.create", + new=AsyncMock( + return_value=Mock( + shutdown=AsyncMock(), + settings=settings, + ) + ), + ), patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.1.1.1"}, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "shelly1pm-12345" + assert result2["data"] == { + "host": "1.1.1.1", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_form_auth(hass): """Test manual configuration if auth is required.""" result = await hass.config_entries.flow.async_init( @@ -78,7 +138,7 @@ async def test_form_auth(hass): new=AsyncMock( return_value=Mock( shutdown=AsyncMock(), - settings={"name": "Test name", "device": {"mac": "test-mac"}}, + settings=MOCK_SETTINGS, ) ), ), patch( @@ -234,18 +294,23 @@ async def test_zeroconf(hass): ): result = await hass.config_entries.flow.async_init( DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + data=DISCOVERY_INFO, context={"source": config_entries.SOURCE_ZEROCONF}, ) assert result["type"] == "form" assert result["errors"] == {} - + context = next( + flow["context"] + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert context["title_placeholders"]["name"] == "shelly1pm-12345" with patch( "aioshelly.Device.create", new=AsyncMock( return_value=Mock( shutdown=AsyncMock(), - settings={"name": "Test name", "device": {"mac": "test-mac"}}, + settings=MOCK_SETTINGS, ) ), ), patch( @@ -269,6 +334,29 @@ async def test_zeroconf(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_zeroconf_with_switch_prefix(hass): + """Test we get remove shelly from the prefix.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "aioshelly.get_info", + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=SWITCH25_DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == "form" + assert result["errors"] == {} + context = next( + flow["context"] + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert context["title_placeholders"]["name"] == "switch25-12345" + + @pytest.mark.parametrize( "error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")] ) @@ -283,7 +371,7 @@ async def test_zeroconf_confirm_error(hass, error): ): result = await hass.config_entries.flow.async_init( DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + data=DISCOVERY_INFO, context={"source": config_entries.SOURCE_ZEROCONF}, ) assert result["type"] == "form" @@ -316,7 +404,7 @@ async def test_zeroconf_already_configured(hass): ): result = await hass.config_entries.flow.async_init( DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + data=DISCOVERY_INFO, context={"source": config_entries.SOURCE_ZEROCONF}, ) assert result["type"] == "abort" @@ -331,7 +419,7 @@ async def test_zeroconf_firmware_unsupported(hass): with patch("aioshelly.get_info", side_effect=aioshelly.FirmwareUnsupported): result = await hass.config_entries.flow.async_init( DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + data=DISCOVERY_INFO, context={"source": config_entries.SOURCE_ZEROCONF}, ) @@ -344,7 +432,7 @@ async def test_zeroconf_cannot_connect(hass): with patch("aioshelly.get_info", side_effect=asyncio.TimeoutError): result = await hass.config_entries.flow.async_init( DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + data=DISCOVERY_INFO, context={"source": config_entries.SOURCE_ZEROCONF}, ) assert result["type"] == "abort" @@ -361,7 +449,7 @@ async def test_zeroconf_require_auth(hass): ): result = await hass.config_entries.flow.async_init( DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + data=DISCOVERY_INFO, context={"source": config_entries.SOURCE_ZEROCONF}, ) assert result["type"] == "form" @@ -379,7 +467,7 @@ async def test_zeroconf_require_auth(hass): new=AsyncMock( return_value=Mock( shutdown=AsyncMock(), - settings={"name": "Test name", "device": {"mac": "test-mac"}}, + settings=MOCK_SETTINGS, ) ), ), patch(