diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 09fc477e512..026021a992f 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -65,6 +65,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH host = None info = None + device_info = None async def async_step_user(self, user_input=None): """Handle the initial step.""" @@ -160,6 +161,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] + + if not info["auth"] and info.get("sleep_mode", False): + try: + self.device_info = await validate_input(self.hass, self.host, {}) + except HTTP_CONNECT_ERRORS: + return self.async_abort(reason="cannot_connect") + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { "name": zeroconf_info.get("name", "").split(".")[0] @@ -173,6 +181,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if self.info["auth"]: return await self.async_step_credentials() + if self.device_info: + return self.async_create_entry( + title=self.device_info["title"] or self.device_info["hostname"], + data={ + "host": self.host, + "sleep_period": self.device_info["sleep_period"], + "model": self.device_info["model"], + }, + ) + try: device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 341328801cc..85a1fa87d0c 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -3,7 +3,7 @@ "flow_title": "{name}", "step": { "user": { - "description": "Before set up, battery-powered devices must be woken up by pressing the button on the device.", + "description": "Before set up, battery-powered devices must be woken up, you can now wake the device up using a button on it.", "data": { "host": "[%key:common::config_flow::data::host%]" } @@ -15,7 +15,7 @@ } }, "confirm_discovery": { - "description": "Do you want to set up the {model} at {host}?\n\nBefore set up, battery-powered devices must be woken up by pressing the button on the device." + "description": "Do you want to set up the {model} at {host}?\n\nBattery-powered devices that are password protected must be woken up before continuing with setting up.\nBattery-powered devices that are not password protected will be added when the device wakes up, you can now manually wake the device up using a button on it or wait for the next data update from the device." } }, "error": { diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 1d5099cec1c..450bf8efb24 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -14,7 +14,6 @@ from tests.common import MockConfigEntry MOCK_SETTINGS = { "name": "Test name", "device": {"mac": "test-mac", "hostname": "test-host", "type": "SHSW-1"}, - "sleep_period": 0, } DISCOVERY_INFO = { "host": "1.1.1.1", @@ -383,6 +382,103 @@ async def test_zeroconf(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_zeroconf_sleeping_device(hass): + """Test sleeping device configuration via zeroconf.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "sleep_mode": True, + }, + ), patch( + "aioshelly.Device.create", + new=AsyncMock( + return_value=Mock( + settings={ + "name": "Test name", + "device": { + "mac": "test-mac", + "hostname": "test-host", + "type": "SHSW-1", + }, + "sleep_mode": {"period": 10, "unit": "m"}, + }, + ) + ), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.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( + "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"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Test name" + assert result2["data"] == { + "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 600, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "error", + [ + (aiohttp.ClientResponseError(Mock(), (), status=400), "cannot_connect"), + (asyncio.TimeoutError, "cannot_connect"), + ], +) +async def test_zeroconf_sleeping_device_error(hass, error): + """Test sleeping device configuration via zeroconf with error.""" + exc = error + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "sleep_mode": True, + }, + ), patch( + "aioshelly.Device.create", + new=AsyncMock(side_effect=exc), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + @pytest.mark.parametrize( "error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")] )