diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index ba005f04a6a..5d90d16f157 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -36,14 +36,22 @@ SENSOR_DICT = { for sensor_id, sensor_spec in SENSOR_TYPES.items() } -DATA_SCHEMA = vol.Schema( - { - vol.Optional(CONF_HOST, default=DEFAULT_HOST): str, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): int, - vol.Optional(CONF_USERNAME): str, - vol.Optional(CONF_PASSWORD): str, - } -) + +def _base_schema(discovery_info): + """Generate base schema.""" + base_schema = {} + if not discovery_info: + base_schema.update( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): str, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): int, + } + ) + base_schema.update( + {vol.Optional(CONF_USERNAME): str, vol.Optional(CONF_PASSWORD): str} + ) + + return vol.Schema(base_schema) def _resource_schema_base(available_resources, selected_resources): @@ -75,7 +83,7 @@ def _ups_schema(ups_list): async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. - Data has the keys from DATA_SCHEMA with values provided by the user. + Data has the keys from _base_schema with values provided by the user. """ host = data[CONF_HOST] @@ -113,9 +121,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the nut config flow.""" self.nut_config = {} self.available_resources = {} + self.discovery_info = {} self.ups_list = None self.title = None + async def async_step_zeroconf(self, discovery_info): + """Prepare configuration for a discovered nut device.""" + self.discovery_info = discovery_info + await self._async_handle_discovery_without_unique_id() + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context["title_placeholders"] = { + CONF_PORT: discovery_info.get(CONF_PORT, DEFAULT_PORT), + CONF_HOST: discovery_info[CONF_HOST], + } + return await self.async_step_user() + async def async_step_import(self, user_input=None): """Handle the import.""" errors = {} @@ -129,13 +149,20 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=title, data=user_input) return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors + step_id="user", data_schema=_base_schema({}), errors=errors ) async def async_step_user(self, user_input=None): """Handle the user input.""" errors = {} if user_input is not None: + if self.discovery_info: + user_input.update( + { + CONF_HOST: self.discovery_info[CONF_HOST], + CONF_PORT: self.discovery_info.get(CONF_PORT, DEFAULT_PORT), + } + ) info, errors = await self._async_validate_or_error(user_input) if not errors: @@ -150,7 +177,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_resources() return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors + step_id="user", data_schema=_base_schema(self.discovery_info), errors=errors ) async def async_step_ups(self, user_input=None): diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 226250b9a52..693b225c6dd 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nut", "requirements": ["pynut2==2.1.2"], "codeowners": ["@bdraco"], - "config_flow": true + "config_flow": true, + "zeroconf": ["_nut._tcp.local."] } diff --git a/homeassistant/components/nut/translations/en.json b/homeassistant/components/nut/translations/en.json index f698ad9287a..ee17c1bfa71 100644 --- a/homeassistant/components/nut/translations/en.json +++ b/homeassistant/components/nut/translations/en.json @@ -1,5 +1,6 @@ { "config": { + "flow_title": "NUT server ({host}:{port})", "abort": { "already_configured": "Device is already configured" }, diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 8c272c49b1e..45e5ad12e04 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -40,6 +40,9 @@ ZEROCONF = { "_miio._udp.local.": [ "xiaomi_miio" ], + "_nut._tcp.local.": [ + "nut" + ], "_printer._tcp.local.": [ "brother" ], diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index 7eb0ac20184..5a2155441b5 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Network UPS Tools (NUT) config flow.""" + from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.nut.const import DOMAIN from homeassistant.const import CONF_RESOURCES, CONF_SCAN_INTERVAL @@ -16,6 +17,59 @@ VALID_CONFIG = { } +async def test_form_zeroconf(hass): + """Test we can setup from zeroconf.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={"host": "192.168.1.5", "port": 1234}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + mock_pynut = _get_mock_pynutclient( + list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] + ) + + with patch( + "homeassistant.components.nut.PyNUTClient", return_value=mock_pynut, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"username": "test-username", "password": "test-password"}, + ) + + assert result2["step_id"] == "resources" + assert result2["type"] == "form" + + with patch( + "homeassistant.components.nut.PyNUTClient", return_value=mock_pynut, + ), patch( + "homeassistant.components.nut.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.nut.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {"resources": ["battery.voltage", "ups.status", "ups.status.display"]}, + ) + + assert result3["type"] == "create_entry" + assert result3["title"] == "192.168.1.5:1234" + assert result3["data"] == { + "host": "192.168.1.5", + "password": "test-password", + "port": 1234, + "resources": ["battery.voltage", "ups.status", "ups.status.display"], + "username": "test-username", + } + assert result3["result"].unique_id is None + 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_user_one_ups(hass): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {})