From 6ef53a2c5d68d6b31b7cdf17737ec75782753661 Mon Sep 17 00:00:00 2001
From: epenet <6771947+epenet@users.noreply.github.com>
Date: Sat, 24 Oct 2020 03:57:16 +0200
Subject: [PATCH] Add config flow for One wire (#39321)

* Add support for config_flow
Add support for switches
Add support for binary sensors

* Add config flow strings

* Update .coveragerc

* black-formatting

* fixes for isort and flake

* fixes for pylint

* fixes for isort

* fixes for isort

* fixes for config_flow

* Add devices to Device Registry

* Updated comments

* fixes for flake8

* Updated comments

* Updated comments

* Implement async_unload_entry

* remove binary_sensor and switch implementation (will move to new PR)

* Update .coveragerc

Co-authored-by: Chris Talkington <chris@talkingtontech.com>

* Update .coveragerc

* Review config flow to store the configuration type

* Add config_flow tests

* Move CONF_NAMES to constants

* Fix isort

* Tweak to onewire logger

* Tweak to onewire logger for sensor

* Reset _LOGGER variable

* Normalise header

* Normalise header

* Update to use references in config flow translations

* Improve test coverage

* fixes for isort

* Update async_unload_entry

* Update common strings

* Update imports

* Move connect attempt to executor

* Update common strings

* Remove OWFS from config_flow

* Prevent duplicate config entries

* Fix isort

* Fix flake8

* Fix tests following removal of OWFS implementation

* Fix flake8

* Adjust config from yaml to config-flow, and add ability to specify sysbus directory

* Ajust unique_id for config entries

* Fix test_config_flow

* Fix invalid merge

* Convert yaml to config_entry

* Update sysbus tests

* Tweaks to yaml import process, and add OWFS warning

* Enable migration of OWFS platform config to config_entry

* update the existing corresponding config entry on OWFS conversion

* Remove CONFIG_SCHEMA

* Move data_schema to constants

* Remove log message

* Remove duplicate warning

* Update already_configured to use already_configured_device constant

* Schedule get_entities on the executor

* Update duplicate entry check for OWServer

* Review TryCatch

* Schedule os.path.isdir on the executor

* rename owhost/owport

* Update checks for empty

* Fix incorrect patch

* Move config_flow validation methods to new OneWireHub

* Fix typo and pre-commit

* Cleanup try/else

* patch async_setup/async_setup_entry

* cleanup implicit exit point

* Fix invalid patch

* cleanup implicit exit point

* cleanup implicit exit point

Co-authored-by: Chris Talkington <chris@talkingtontech.com>
---
 homeassistant/components/onewire/__init__.py  |  30 ++
 .../components/onewire/config_flow.py         | 195 ++++++++++
 homeassistant/components/onewire/const.py     |   1 +
 .../components/onewire/manifest.json          |   1 +
 .../components/onewire/onewirehub.py          |  35 ++
 homeassistant/components/onewire/sensor.py    |  53 +--
 homeassistant/components/onewire/strings.json |  26 ++
 .../components/onewire/translations/en.json   |  26 ++
 homeassistant/generated/config_flows.py       |   1 +
 tests/components/onewire/__init__.py          |  59 ++++
 tests/components/onewire/test_config_flow.py  | 333 ++++++++++++++++++
 .../components/onewire/test_entity_sysbus.py  |  15 +-
 tests/components/onewire/test_init.py         |  23 ++
 tests/components/onewire/test_sensor.py       |   8 +-
 14 files changed, 774 insertions(+), 32 deletions(-)
 create mode 100644 homeassistant/components/onewire/config_flow.py
 create mode 100644 homeassistant/components/onewire/onewirehub.py
 create mode 100644 homeassistant/components/onewire/strings.json
 create mode 100644 homeassistant/components/onewire/translations/en.json
 create mode 100644 tests/components/onewire/test_config_flow.py
 create mode 100644 tests/components/onewire/test_init.py

diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py
index 21dc0c2ead3..e228856a7fd 100644
--- a/homeassistant/components/onewire/__init__.py
+++ b/homeassistant/components/onewire/__init__.py
@@ -1 +1,31 @@
 """The 1-Wire component."""
+import asyncio
+
+from .const import SUPPORTED_PLATFORMS
+
+
+async def async_setup(hass, config):
+    """Set up 1-Wire integrations."""
+    return True
+
+
+async def async_setup_entry(hass, config_entry):
+    """Set up a 1-Wire proxy for a config entry."""
+    for component in SUPPORTED_PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(config_entry, component)
+        )
+    return True
+
+
+async def async_unload_entry(hass, config_entry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(config_entry, component)
+                for component in SUPPORTED_PLATFORMS
+            ]
+        )
+    )
+    return unload_ok
diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py
new file mode 100644
index 00000000000..7cd963c6996
--- /dev/null
+++ b/homeassistant/components/onewire/config_flow.py
@@ -0,0 +1,195 @@
+"""Config flow for 1-Wire component."""
+import voluptuous as vol
+
+from homeassistant import exceptions
+from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow
+from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
+from homeassistant.helpers.typing import HomeAssistantType
+
+from .const import (  # pylint: disable=unused-import
+    CONF_MOUNT_DIR,
+    CONF_TYPE_OWFS,
+    CONF_TYPE_OWSERVER,
+    CONF_TYPE_SYSBUS,
+    DEFAULT_OWSERVER_HOST,
+    DEFAULT_OWSERVER_PORT,
+    DEFAULT_SYSBUS_MOUNT_DIR,
+    DOMAIN,
+)
+from .onewirehub import OneWireHub
+
+DATA_SCHEMA_USER = vol.Schema(
+    {vol.Required(CONF_TYPE): vol.In([CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS])}
+)
+DATA_SCHEMA_OWSERVER = vol.Schema(
+    {
+        vol.Required(CONF_HOST, default=DEFAULT_OWSERVER_HOST): str,
+        vol.Required(CONF_PORT, default=DEFAULT_OWSERVER_PORT): int,
+    }
+)
+DATA_SCHEMA_MOUNTDIR = vol.Schema(
+    {
+        vol.Required(CONF_MOUNT_DIR, default=DEFAULT_SYSBUS_MOUNT_DIR): str,
+    }
+)
+
+
+async def validate_input_owserver(hass: HomeAssistantType, data):
+    """Validate the user input allows us to connect.
+
+    Data has the keys from DATA_SCHEMA_OWSERVER with values provided by the user.
+    """
+
+    hub = OneWireHub(hass)
+
+    host = data[CONF_HOST]
+    port = data[CONF_PORT]
+    if not await hub.can_connect(host, port):
+        raise CannotConnect
+
+    # Return info that you want to store in the config entry.
+    return {"title": host}
+
+
+def is_duplicate_owserver_entry(hass: HomeAssistantType, user_input):
+    """Check existing entries for matching host and port."""
+    for config_entry in hass.config_entries.async_entries(DOMAIN):
+        if (
+            config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER
+            and config_entry.data[CONF_HOST] == user_input[CONF_HOST]
+            and config_entry.data[CONF_PORT] == str(user_input[CONF_PORT])
+        ):
+            return True
+    return False
+
+
+async def validate_input_mount_dir(hass: HomeAssistantType, data):
+    """Validate the user input allows us to connect.
+
+    Data has the keys from DATA_SCHEMA_MOUNTDIR with values provided by the user.
+    """
+    hub = OneWireHub(hass)
+
+    mount_dir = data[CONF_MOUNT_DIR]
+    if not await hub.is_valid_mount_dir(mount_dir):
+        raise InvalidPath
+
+    # Return info that you want to store in the config entry.
+    return {"title": mount_dir}
+
+
+class OneWireFlowHandler(ConfigFlow, domain=DOMAIN):
+    """Handle 1-Wire config flow."""
+
+    VERSION = 1
+    CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL
+
+    def __init__(self):
+        """Initialize 1-Wire config flow."""
+        self.onewire_config = {}
+
+    async def async_step_user(self, user_input=None):
+        """Handle 1-Wire config flow start.
+
+        Let user manually input configuration.
+        """
+        errors = {}
+        if user_input is not None:
+            self.onewire_config.update(user_input)
+            if CONF_TYPE_OWSERVER == user_input[CONF_TYPE]:
+                return await self.async_step_owserver()
+            if CONF_TYPE_SYSBUS == user_input[CONF_TYPE]:
+                return await self.async_step_mount_dir()
+
+        return self.async_show_form(
+            step_id="user",
+            data_schema=DATA_SCHEMA_USER,
+            errors=errors,
+        )
+
+    async def async_step_owserver(self, user_input=None):
+        """Handle OWServer configuration."""
+        errors = {}
+        if user_input:
+            # Prevent duplicate entries
+            if is_duplicate_owserver_entry(self.hass, user_input):
+                return self.async_abort(reason="already_configured")
+
+            self.onewire_config.update(user_input)
+
+            try:
+                info = await validate_input_owserver(self.hass, user_input)
+            except CannotConnect:
+                errors["base"] = "cannot_connect"
+            else:
+                return self.async_create_entry(
+                    title=info["title"], data=self.onewire_config
+                )
+
+        return self.async_show_form(
+            step_id="owserver",
+            data_schema=DATA_SCHEMA_OWSERVER,
+            errors=errors,
+        )
+
+    async def async_step_mount_dir(self, user_input=None):
+        """Handle SysBus configuration."""
+        errors = {}
+        if user_input:
+            # Prevent duplicate entries
+            await self.async_set_unique_id(
+                f"{CONF_TYPE_SYSBUS}:{user_input[CONF_MOUNT_DIR]}"
+            )
+            self._abort_if_unique_id_configured()
+
+            self.onewire_config.update(user_input)
+
+            try:
+                info = await validate_input_mount_dir(self.hass, user_input)
+            except InvalidPath:
+                errors["base"] = "invalid_path"
+            else:
+                return self.async_create_entry(
+                    title=info["title"], data=self.onewire_config
+                )
+
+        return self.async_show_form(
+            step_id="mount_dir",
+            data_schema=DATA_SCHEMA_MOUNTDIR,
+            errors=errors,
+        )
+
+    async def async_step_import(self, platform_config):
+        """Handle import configuration from YAML."""
+        # OWServer
+        if platform_config[CONF_TYPE] == CONF_TYPE_OWSERVER:
+            if CONF_PORT not in platform_config:
+                platform_config[CONF_PORT] = DEFAULT_OWSERVER_PORT
+            return await self.async_step_owserver(platform_config)
+
+        # OWFS
+        if platform_config[CONF_TYPE] == CONF_TYPE_OWFS:  # pragma: no cover
+            # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
+            # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
+            await self.async_set_unique_id(
+                f"{CONF_TYPE_OWFS}:{platform_config[CONF_MOUNT_DIR]}"
+            )
+            self._abort_if_unique_id_configured(
+                updates=platform_config, reload_on_update=True
+            )
+            return self.async_create_entry(
+                title=platform_config[CONF_MOUNT_DIR], data=platform_config
+            )
+
+        # SysBus
+        if CONF_MOUNT_DIR not in platform_config:
+            platform_config[CONF_MOUNT_DIR] = DEFAULT_SYSBUS_MOUNT_DIR
+        return await self.async_step_mount_dir(platform_config)
+
+
+class CannotConnect(exceptions.HomeAssistantError):
+    """Error to indicate we cannot connect."""
+
+
+class InvalidPath(exceptions.HomeAssistantError):
+    """Error to indicate the path is invalid."""
diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py
index 83ffa12706c..eade0d11358 100644
--- a/homeassistant/components/onewire/const.py
+++ b/homeassistant/components/onewire/const.py
@@ -8,6 +8,7 @@ CONF_TYPE_OWFS = "OWFS"
 CONF_TYPE_OWSERVER = "OWServer"
 CONF_TYPE_SYSBUS = "SysBus"
 
+DEFAULT_OWSERVER_HOST = "localhost"
 DEFAULT_OWSERVER_PORT = 4304
 DEFAULT_SYSBUS_MOUNT_DIR = "/sys/bus/w1/devices/"
 
diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json
index 42ef7e54603..47ab6ad2404 100644
--- a/homeassistant/components/onewire/manifest.json
+++ b/homeassistant/components/onewire/manifest.json
@@ -2,6 +2,7 @@
   "domain": "onewire",
   "name": "1-Wire",
   "documentation": "https://www.home-assistant.io/integrations/onewire",
+  "config_flow": true,
   "requirements": ["pyownet==0.10.0.post1", "pi1wire==0.1.0"],
   "codeowners": ["@garbled1", "@epenet"]
 }
diff --git a/homeassistant/components/onewire/onewirehub.py b/homeassistant/components/onewire/onewirehub.py
new file mode 100644
index 00000000000..22bb0098f92
--- /dev/null
+++ b/homeassistant/components/onewire/onewirehub.py
@@ -0,0 +1,35 @@
+"""Hub for communication with 1-Wire server or mount_dir."""
+import logging
+import os
+
+from pyownet import protocol
+
+from homeassistant.helpers.typing import HomeAssistantType
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class OneWireHub:
+    """Hub to communicate with SysBus or OWServer."""
+
+    def __init__(self, hass: HomeAssistantType):
+        """Initialize."""
+        self.hass = hass
+
+    async def can_connect(self, host, port) -> bool:
+        """Test if we can authenticate with the host."""
+        try:
+            await self.hass.async_add_executor_job(protocol.proxy, host, port)
+        except (protocol.Error, protocol.ConnError) as exc:
+            _LOGGER.error(
+                "Cannot connect to owserver on %s:%d, got: %s", host, port, exc
+            )
+            return False
+        return True
+
+    async def is_valid_mount_dir(self, mount_dir) -> bool:
+        """Test that the mount_dir is a valid path."""
+        if not await self.hass.async_add_executor_job(os.path.isdir, mount_dir):
+            _LOGGER.error("Cannot find directory %s", mount_dir)
+            return False
+        return True
diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py
index bce17dbf3cb..7952e5ca7de 100644
--- a/homeassistant/components/onewire/sensor.py
+++ b/homeassistant/components/onewire/sensor.py
@@ -8,9 +8,11 @@ from pyownet import protocol
 import voluptuous as vol
 
 from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.config_entries import SOURCE_IMPORT
 from homeassistant.const import (
     CONF_HOST,
     CONF_PORT,
+    CONF_TYPE,
     ELECTRICAL_CURRENT_AMPERE,
     LIGHT_LUX,
     PERCENTAGE,
@@ -29,6 +31,7 @@ from .const import (
     CONF_TYPE_SYSBUS,
     DEFAULT_OWSERVER_PORT,
     DEFAULT_SYSBUS_MOUNT_DIR,
+    DOMAIN,
     PRESSURE_CBAR,
 )
 
@@ -125,36 +128,44 @@ def hb_info_from_type(dev_type="std"):
         return HOBBYBOARD_EF
 
 
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
+    """Old way of setting up 1-Wire platform."""
+    if config.get(CONF_HOST):
+        config[CONF_TYPE] = CONF_TYPE_OWSERVER
+    elif config[CONF_MOUNT_DIR] == DEFAULT_SYSBUS_MOUNT_DIR:
+        config[CONF_TYPE] = CONF_TYPE_SYSBUS
+    else:  # pragma: no cover
+        # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
+        # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
+        config[CONF_TYPE] = CONF_TYPE_OWFS
+
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=config
+        )
+    )
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
     """Set up 1-Wire platform."""
-    entities = get_entities(config)
-    add_entities(entities, True)
+    entities = await hass.async_add_executor_job(get_entities, config_entry.data)
+    async_add_entities(entities, True)
 
 
 def get_entities(config):
     """Get a list of entities."""
-    base_dir = config[CONF_MOUNT_DIR]
-    owhost = config.get(CONF_HOST)
-    owport = config[CONF_PORT]
-
-    # Ensure type is configured
-    if owhost:
-        conf_type = CONF_TYPE_OWSERVER
-    elif base_dir == DEFAULT_SYSBUS_MOUNT_DIR:
-        conf_type = CONF_TYPE_SYSBUS
-    else:  # pragma: no cover
-        # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
-        # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
-        conf_type = CONF_TYPE_OWFS
-
     entities = []
     device_names = {}
     if CONF_NAMES in config:
         if isinstance(config[CONF_NAMES], dict):
             device_names = config[CONF_NAMES]
 
+    conf_type = config[CONF_TYPE]
     # We have an owserver on a remote(or local) host/port
     if conf_type == CONF_TYPE_OWSERVER:
+        owhost = config[CONF_HOST]
+        owport = config[CONF_PORT]
+
         _LOGGER.debug("Initializing using %s:%s", owhost, owport)
         try:
             owproxy = protocol.proxy(host=owhost, port=owport)
@@ -163,7 +174,7 @@ def get_entities(config):
             _LOGGER.error(
                 "Cannot connect to owserver on %s:%d, got: %s", owhost, owport, exc
             )
-            devices = []
+            return entities
         for device in devices:
             _LOGGER.debug("Found device: %s", device)
             family = owproxy.read(f"{device}family").decode()
@@ -200,8 +211,9 @@ def get_entities(config):
 
     # We have a raw GPIO ow sensor on a Pi
     elif conf_type == CONF_TYPE_SYSBUS:
-        _LOGGER.debug("Initializing using SysBus")
-        for p1sensor in Pi1Wire().find_all_sensors():
+        base_dir = config[CONF_MOUNT_DIR]
+        _LOGGER.debug("Initializing using SysBus %s", base_dir)
+        for p1sensor in Pi1Wire(base_dir).find_all_sensors():
             family = p1sensor.mac_address[:2]
             sensor_id = f"{family}-{p1sensor.mac_address[2:]}"
             if family not in DEVICE_SUPPORT_SYSBUS:
@@ -232,6 +244,7 @@ def get_entities(config):
     else:  # pragma: no cover
         # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
         # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
+        base_dir = config[CONF_MOUNT_DIR]
         _LOGGER.debug("Initializing using OWFS %s", base_dir)
         _LOGGER.warning(
             "The OWFS implementation of 1-Wire sensors is deprecated, "
diff --git a/homeassistant/components/onewire/strings.json b/homeassistant/components/onewire/strings.json
new file mode 100644
index 00000000000..928907b319a
--- /dev/null
+++ b/homeassistant/components/onewire/strings.json
@@ -0,0 +1,26 @@
+{
+  "config": {
+    "abort": {
+      "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
+    },
+    "error": {
+      "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
+      "invalid_path": "Directory not found."
+    },
+    "step": {
+      "owserver": {
+        "data": {
+          "host": "[%key:common::config_flow::data::host%]",
+          "port": "[%key:common::config_flow::data::port%]"
+        },
+        "title": "Set owserver details"
+      },
+      "user": {
+        "data": {
+          "type": "Connection type"
+        },
+        "title": "Set up 1-Wire"
+      }
+    }
+  }
+}
diff --git a/homeassistant/components/onewire/translations/en.json b/homeassistant/components/onewire/translations/en.json
new file mode 100644
index 00000000000..ec71701bf98
--- /dev/null
+++ b/homeassistant/components/onewire/translations/en.json
@@ -0,0 +1,26 @@
+{
+    "config": {
+        "abort": {
+            "already_configured": "Service is already configured"
+          },
+        "error": {
+            "cannot_connect": "Unable to connect.",
+            "invalid_path": "Directory not found."
+        },
+        "step": {
+            "owserver": {
+                "data": {
+                    "host": "Host",
+                    "port": "Port"
+                },
+                "title": "Set owserver details"
+            },
+            "user": {
+                "data": {
+                    "type": "Connection type"
+                },
+                "title": "Set up 1-Wire"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index bd1a1ed4e53..f94c76417b4 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -132,6 +132,7 @@ FLOWS = [
     "nws",
     "nzbget",
     "omnilogic",
+    "onewire",
     "onvif",
     "opentherm_gw",
     "openuv",
diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py
index e5de65bc71e..9d9f4148fb4 100644
--- a/tests/components/onewire/__init__.py
+++ b/tests/components/onewire/__init__.py
@@ -1 +1,60 @@
 """Tests for 1-Wire integration."""
+from asynctest.mock import patch
+
+from homeassistant.components.onewire.const import (
+    CONF_TYPE_OWSERVER,
+    CONF_TYPE_SYSBUS,
+    DEFAULT_SYSBUS_MOUNT_DIR,
+    DOMAIN,
+)
+from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL
+from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
+
+from tests.common import MockConfigEntry
+
+
+async def setup_onewire_sysbus_integration(hass):
+    """Create the 1-Wire integration."""
+    config_entry = MockConfigEntry(
+        domain=DOMAIN,
+        source="user",
+        data={
+            CONF_TYPE: CONF_TYPE_SYSBUS,
+        },
+        unique_id=f"{CONF_TYPE_SYSBUS}:{DEFAULT_SYSBUS_MOUNT_DIR}",
+        connection_class=CONN_CLASS_LOCAL_POLL,
+        options={},
+        entry_id="1",
+    )
+    config_entry.add_to_hass(hass)
+
+    await hass.config_entries.async_setup(config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    return config_entry
+
+
+async def setup_onewire_owserver_integration(hass):
+    """Create the 1-Wire integration."""
+    config_entry = MockConfigEntry(
+        domain=DOMAIN,
+        source="user",
+        data={
+            CONF_TYPE: CONF_TYPE_OWSERVER,
+            CONF_HOST: "1.2.3.4",
+            CONF_PORT: "1234",
+        },
+        unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
+        connection_class=CONN_CLASS_LOCAL_POLL,
+        options={},
+        entry_id="2",
+    )
+    config_entry.add_to_hass(hass)
+
+    with patch(
+        "homeassistant.components.onewire.sensor.protocol.proxy",
+    ):
+        await hass.config_entries.async_setup(config_entry.entry_id)
+        await hass.async_block_till_done()
+
+        return config_entry
diff --git a/tests/components/onewire/test_config_flow.py b/tests/components/onewire/test_config_flow.py
new file mode 100644
index 00000000000..dfb64a3846e
--- /dev/null
+++ b/tests/components/onewire/test_config_flow.py
@@ -0,0 +1,333 @@
+"""Tests for 1-Wire config flow."""
+from pyownet import protocol
+
+from homeassistant.components.onewire.const import (
+    CONF_MOUNT_DIR,
+    CONF_TYPE_OWSERVER,
+    CONF_TYPE_SYSBUS,
+    DEFAULT_OWSERVER_PORT,
+    DEFAULT_SYSBUS_MOUNT_DIR,
+    DOMAIN,
+)
+from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
+from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
+from homeassistant.data_entry_flow import (
+    RESULT_TYPE_ABORT,
+    RESULT_TYPE_CREATE_ENTRY,
+    RESULT_TYPE_FORM,
+)
+
+from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration
+
+from tests.async_mock import patch
+
+
+async def test_user_owserver(hass):
+    """Test OWServer user flow."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+    assert result["type"] == RESULT_TYPE_FORM
+    assert not result["errors"]
+
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_TYPE: CONF_TYPE_OWSERVER},
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["step_id"] == "owserver"
+    assert not result["errors"]
+
+    # Invalid server
+    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={CONF_HOST: "1.2.3.4", CONF_PORT: 1234},
+        )
+
+        assert result["type"] == RESULT_TYPE_FORM
+        assert result["step_id"] == "owserver"
+        assert result["errors"] == {"base": "cannot_connect"}
+
+    # Valid server
+    with patch("homeassistant.components.onewire.onewirehub.protocol.proxy",), patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            user_input={CONF_HOST: "1.2.3.4", CONF_PORT: 1234},
+        )
+
+        assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+        assert result["title"] == "1.2.3.4"
+        assert result["data"] == {
+            CONF_TYPE: CONF_TYPE_OWSERVER,
+            CONF_HOST: "1.2.3.4",
+            CONF_PORT: 1234,
+        }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_user_owserver_duplicate(hass):
+    """Test OWServer flow."""
+    with patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        await setup_onewire_owserver_integration(hass)
+        assert len(hass.config_entries.async_entries(DOMAIN)) == 1
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+    assert result["type"] == RESULT_TYPE_FORM
+    assert not result["errors"]
+
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_TYPE: CONF_TYPE_OWSERVER},
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["step_id"] == "owserver"
+    assert not result["errors"]
+
+    # Duplicate server
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_HOST: "1.2.3.4", CONF_PORT: 1234},
+    )
+    assert result["type"] == RESULT_TYPE_ABORT
+    assert result["reason"] == "already_configured"
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_user_sysbus(hass):
+    """Test SysBus flow."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+    assert result["type"] == RESULT_TYPE_FORM
+    assert not result["errors"]
+
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_TYPE: CONF_TYPE_SYSBUS},
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["step_id"] == "mount_dir"
+    assert not result["errors"]
+
+    # Invalid path
+    with patch(
+        "homeassistant.components.onewire.onewirehub.os.path.isdir",
+        return_value=False,
+    ):
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            user_input={CONF_MOUNT_DIR: "/sys/bus/invalid_directory"},
+        )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["step_id"] == "mount_dir"
+    assert result["errors"] == {"base": "invalid_path"}
+
+    # Valid path
+    with patch(
+        "homeassistant.components.onewire.onewirehub.os.path.isdir",
+        return_value=True,
+    ), patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            user_input={CONF_MOUNT_DIR: "/sys/bus/directory"},
+        )
+
+    assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == "/sys/bus/directory"
+    assert result["data"] == {
+        CONF_TYPE: CONF_TYPE_SYSBUS,
+        CONF_MOUNT_DIR: "/sys/bus/directory",
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_user_sysbus_duplicate(hass):
+    """Test SysBus duplicate flow."""
+    with patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        await setup_onewire_sysbus_integration(hass)
+        assert len(hass.config_entries.async_entries(DOMAIN)) == 1
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+    assert result["type"] == RESULT_TYPE_FORM
+    assert not result["errors"]
+
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_TYPE: CONF_TYPE_SYSBUS},
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["step_id"] == "mount_dir"
+    assert not result["errors"]
+
+    # Valid path
+    with patch(
+        "homeassistant.components.onewire.onewirehub.os.path.isdir",
+        return_value=True,
+    ):
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            user_input={CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR},
+        )
+
+    assert result["type"] == RESULT_TYPE_ABORT
+    assert result["reason"] == "already_configured"
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_import_sysbus(hass):
+    """Test import step."""
+
+    with patch(
+        "homeassistant.components.onewire.onewirehub.os.path.isdir",
+        return_value=True,
+    ), patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={CONF_TYPE: CONF_TYPE_SYSBUS},
+        )
+    assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == DEFAULT_SYSBUS_MOUNT_DIR
+    assert result["data"] == {
+        CONF_TYPE: CONF_TYPE_SYSBUS,
+        CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR,
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_import_sysbus_with_mount_dir(hass):
+    """Test import step."""
+
+    with patch(
+        "homeassistant.components.onewire.onewirehub.os.path.isdir",
+        return_value=True,
+    ), patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={
+                CONF_TYPE: CONF_TYPE_SYSBUS,
+                CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR,
+            },
+        )
+    assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == DEFAULT_SYSBUS_MOUNT_DIR
+    assert result["data"] == {
+        CONF_TYPE: CONF_TYPE_SYSBUS,
+        CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR,
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_import_owserver(hass):
+    """Test import step."""
+
+    with patch("homeassistant.components.onewire.onewirehub.protocol.proxy",), patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={
+                CONF_TYPE: CONF_TYPE_OWSERVER,
+                CONF_HOST: "1.2.3.4",
+            },
+        )
+    assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == "1.2.3.4"
+    assert result["data"] == {
+        CONF_TYPE: CONF_TYPE_OWSERVER,
+        CONF_HOST: "1.2.3.4",
+        CONF_PORT: DEFAULT_OWSERVER_PORT,
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_import_owserver_with_port(hass):
+    """Test import step."""
+
+    with patch("homeassistant.components.onewire.onewirehub.protocol.proxy",), patch(
+        "homeassistant.components.onewire.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.onewire.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": SOURCE_IMPORT},
+            data={
+                CONF_TYPE: CONF_TYPE_OWSERVER,
+                CONF_HOST: "1.2.3.4",
+                CONF_PORT: "1234",
+            },
+        )
+    assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == "1.2.3.4"
+    assert result["data"] == {
+        CONF_TYPE: CONF_TYPE_OWSERVER,
+        CONF_HOST: "1.2.3.4",
+        CONF_PORT: "1234",
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
diff --git a/tests/components/onewire/test_entity_sysbus.py b/tests/components/onewire/test_entity_sysbus.py
index 8a233315ab2..ba6f5fb62c2 100644
--- a/tests/components/onewire/test_entity_sysbus.py
+++ b/tests/components/onewire/test_entity_sysbus.py
@@ -1,5 +1,5 @@
 """Tests for 1-Wire devices connected on SysBus."""
-from unittest.mock import PropertyMock, patch
+from unittest.mock import patch
 
 from pi1wire import InvalidCRCException, UnsupportResponseException
 import pytest
@@ -93,19 +93,18 @@ async def test_onewiredirect_setup_valid_device(hass, device_id):
     """Test that sysbus config entry works correctly."""
     entity_registry = mock_registry(hass)
 
+    glob_result = [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"]
     read_side_effect = []
     expected_sensors = MOCK_DEVICE_SENSORS[device_id]["sensors"]
     for expected_sensor in expected_sensors:
         read_side_effect.append(expected_sensor["injected_value"])
 
     with patch(
-        "homeassistant.components.onewire.sensor.Pi1Wire"
-    ) as mock_pi1wire, patch("pi1wire.OneWire") as mock_owsensor:
-        type(mock_owsensor).mac_address = PropertyMock(
-            return_value=device_id.replace("-", "")
-        )
-        mock_owsensor.get_temperature.side_effect = read_side_effect
-        mock_pi1wire.return_value.find_all_sensors.return_value = [mock_owsensor]
+        "homeassistant.components.onewire.sensor.os.path.isdir", return_value=True
+    ), patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch(
+        "pi1wire.OneWire.get_temperature",
+        side_effect=read_side_effect,
+    ):
         assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG)
         await hass.async_block_till_done()
 
diff --git a/tests/components/onewire/test_init.py b/tests/components/onewire/test_init.py
new file mode 100644
index 00000000000..6c09d0a595f
--- /dev/null
+++ b/tests/components/onewire/test_init.py
@@ -0,0 +1,23 @@
+"""Tests for 1-Wire config flow."""
+from homeassistant.components.onewire.const import DOMAIN
+from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED
+
+from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration
+
+
+async def test_unload_entry(hass):
+    """Test being able to unload an entry."""
+    config_entry_owserver = await setup_onewire_owserver_integration(hass)
+    config_entry_sysbus = await setup_onewire_sysbus_integration(hass)
+
+    assert len(hass.config_entries.async_entries(DOMAIN)) == 2
+    assert config_entry_owserver.state == ENTRY_STATE_LOADED
+    assert config_entry_sysbus.state == ENTRY_STATE_LOADED
+
+    assert await hass.config_entries.async_unload(config_entry_owserver.entry_id)
+    assert await hass.config_entries.async_unload(config_entry_sysbus.entry_id)
+    await hass.async_block_till_done()
+
+    assert config_entry_owserver.state == ENTRY_STATE_NOT_LOADED
+    assert config_entry_sysbus.state == ENTRY_STATE_NOT_LOADED
+    assert not hass.data.get(DOMAIN)
diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py
index 0aa02a9906d..751ef106147 100644
--- a/tests/components/onewire/test_sensor.py
+++ b/tests/components/onewire/test_sensor.py
@@ -7,7 +7,7 @@ from tests.common import assert_setup_component
 
 
 async def test_setup_minimum(hass):
-    """Test setup with minimum configuration."""
+    """Test old platform setup with minimum configuration."""
     config = {"sensor": {"platform": "onewire"}}
     with assert_setup_component(1, "sensor"):
         assert await async_setup_component(hass, sensor.DOMAIN, config)
@@ -15,7 +15,7 @@ async def test_setup_minimum(hass):
 
 
 async def test_setup_sysbus(hass):
-    """Test setup with SysBus configuration."""
+    """Test old platform setup with SysBus configuration."""
     config = {
         "sensor": {
             "platform": "onewire",
@@ -28,7 +28,7 @@ async def test_setup_sysbus(hass):
 
 
 async def test_setup_owserver(hass):
-    """Test setup with OWServer configuration."""
+    """Test old platform setup with OWServer configuration."""
     config = {"sensor": {"platform": "onewire", "host": "localhost"}}
     with assert_setup_component(1, "sensor"):
         assert await async_setup_component(hass, sensor.DOMAIN, config)
@@ -36,7 +36,7 @@ async def test_setup_owserver(hass):
 
 
 async def test_setup_owserver_with_port(hass):
-    """Test setup with OWServer configuration."""
+    """Test old platform setup with OWServer configuration."""
     config = {"sensor": {"platform": "onewire", "host": "localhost", "port": "1234"}}
     with assert_setup_component(1, "sensor"):
         assert await async_setup_component(hass, sensor.DOMAIN, config)