diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 9ab8dc32f68..c5d4bb065e0 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -8,6 +8,7 @@ from typing import Any from pyownet import protocol import voluptuous as vol +from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant, callback @@ -115,6 +116,19 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): } return await self.async_step_discovery_confirm() + async def async_step_zeroconf( + self, discovery_info: zeroconf.ZeroconfServiceInfo + ) -> ConfigFlowResult: + """Handle zeroconf discovery.""" + await self._async_handle_discovery_without_unique_id() + + self._discovery_data = { + "title": discovery_info.name, + CONF_HOST: discovery_info.hostname, + CONF_PORT: discovery_info.port, + } + return await self.async_step_discovery_confirm() + async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index 4f3cb5d04ab..844c4c1afb9 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -7,5 +7,6 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["pyownet"], - "requirements": ["pyownet==0.10.0.post1"] + "requirements": ["pyownet==0.10.0.post1"], + "zeroconf": ["_owserver._tcp.local."] } diff --git a/homeassistant/components/onewire/quality_scale.yaml b/homeassistant/components/onewire/quality_scale.yaml index b64bfb775ce..d46ed69f0d6 100644 --- a/homeassistant/components/onewire/quality_scale.yaml +++ b/homeassistant/components/onewire/quality_scale.yaml @@ -67,9 +67,7 @@ rules: entity-disabled-by-default: done discovery: status: done - comment: | - hassio discovery implemented, mDNS/zeroconf should also be possible - https://owfs.org/index_php_page_avahi-discovery.html (see PR 135295) + comment: hassio and mDNS/zeroconf discovery implemented stale-devices: status: done comment: > diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 0766e1ce011..203f01e7d68 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -729,6 +729,11 @@ ZEROCONF = { "domain": "octoprint", }, ], + "_owserver._tcp.local.": [ + { + "domain": "onewire", + }, + ], "_plexmediasvr._tcp.local.": [ { "domain": "plex", diff --git a/tests/components/onewire/test_config_flow.py b/tests/components/onewire/test_config_flow.py index 09a7cdfcbb0..db551c0a1c3 100644 --- a/tests/components/onewire/test_config_flow.py +++ b/tests/components/onewire/test_config_flow.py @@ -1,5 +1,6 @@ """Tests for 1-Wire config flow.""" +from ipaddress import ip_address from unittest.mock import AsyncMock, patch from pyownet import protocol @@ -11,7 +12,8 @@ from homeassistant.components.onewire.const import ( INPUT_ENTRY_DEVICE_SELECTION, MANUFACTURER_MAXIM, ) -from homeassistant.config_entries import SOURCE_HASSIO, SOURCE_USER +from homeassistant.components.zeroconf import ZeroconfServiceInfo +from homeassistant.config_entries import SOURCE_HASSIO, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -26,6 +28,15 @@ _HASSIO_DISCOVERY = HassioServiceInfo( slug="1302b8e0_owserver", uuid="e3fa56560d93458b96a594cbcea3017e", ) +_ZEROCONF_DISCOVERY = ZeroconfServiceInfo( + ip_address=ip_address("5.6.7.8"), + ip_addresses=[ip_address("5.6.7.8")], + hostname="ubuntu.local.", + name="OWFS (1-wire) Server", + port=4304, + type="_owserver._tcp.local.", + properties={}, +) pytestmark = pytest.mark.usefixtures("mock_setup_entry") @@ -250,6 +261,58 @@ async def test_hassio_duplicate(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" +async def test_zeroconf_flow(hass: HomeAssistant) -> None: + """Test zeroconf discovery flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=_ZEROCONF_DISCOVERY, + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "discovery_confirm" + assert not result["errors"] + + # Cannot connect to server => retry + with patch( + "homeassistant.components.onewire.onewirehub.protocol.proxy", + side_effect=protocol.ConnError, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "discovery_confirm" + assert result["errors"] == {"base": "cannot_connect"} + + # Connect OK + with patch( + "homeassistant.components.onewire.onewirehub.protocol.proxy", + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + new_entry = result["result"] + assert new_entry.title == "OWFS (1-wire) Server" + assert new_entry.data == {CONF_HOST: "ubuntu.local.", CONF_PORT: 4304} + + +@pytest.mark.usefixtures("config_entry") +async def test_zeroconf_duplicate(hass: HomeAssistant) -> None: + """Test zeroconf discovery duplicate flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=_ZEROCONF_DISCOVERY, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + @pytest.mark.usefixtures("filled_device_registry") async def test_user_options_clear( hass: HomeAssistant, config_entry: MockConfigEntry