diff --git a/.coveragerc b/.coveragerc
index 67bea8dae16089ed2f9cfa6ccb51ad18873666f6..ece1c1258211882a26badd3dccf0c0260cce6dc5 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -214,7 +214,14 @@ omit =
     homeassistant/components/emoncms_history/*
     homeassistant/components/emulated_hue/upnp.py
     homeassistant/components/enigma2/media_player.py
-    homeassistant/components/enocean/*
+    homeassistant/components/enocean/__init__.py
+    homeassistant/components/enocean/binary_sensor.py
+    homeassistant/components/enocean/const.py
+    homeassistant/components/enocean/device.py
+    homeassistant/components/enocean/dongle.py
+    homeassistant/components/enocean/light.py
+    homeassistant/components/enocean/sensor.py
+    homeassistant/components/enocean/switch.py
     homeassistant/components/enphase_envoy/sensor.py
     homeassistant/components/entur_public_transport/*
     homeassistant/components/environment_canada/*
diff --git a/homeassistant/components/enocean/__init__.py b/homeassistant/components/enocean/__init__.py
index 90ab408775414a39b38e5de7525f40d4c32e7b78..c13f71a14321c8b89c8b8fd1f704975a86c4a234 100644
--- a/homeassistant/components/enocean/__init__.py
+++ b/homeassistant/components/enocean/__init__.py
@@ -1,93 +1,57 @@
 """Support for EnOcean devices."""
-import logging
 
-from enocean.communicators.serialcommunicator import SerialCommunicator
-from enocean.protocol.packet import Packet, RadioPacket
-from enocean.utils import combine_hex
 import voluptuous as vol
 
+from homeassistant import config_entries, core
+from homeassistant.config_entries import SOURCE_IMPORT
 from homeassistant.const import CONF_DEVICE
 import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import Entity
 
-_LOGGER = logging.getLogger(__name__)
-
-DOMAIN = "enocean"
-DATA_ENOCEAN = "enocean"
+from .const import DATA_ENOCEAN, DOMAIN, ENOCEAN_DONGLE
+from .dongle import EnOceanDongle
 
 CONFIG_SCHEMA = vol.Schema(
     {DOMAIN: vol.Schema({vol.Required(CONF_DEVICE): cv.string})}, extra=vol.ALLOW_EXTRA
 )
 
-SIGNAL_RECEIVE_MESSAGE = "enocean.receive_message"
-SIGNAL_SEND_MESSAGE = "enocean.send_message"
-
 
-def setup(hass, config):
+async def async_setup(hass, config):
     """Set up the EnOcean component."""
-    serial_dev = config[DOMAIN].get(CONF_DEVICE)
-    dongle = EnOceanDongle(hass, serial_dev)
-    hass.data[DATA_ENOCEAN] = dongle
-
-    return True
-
-
-class EnOceanDongle:
-    """Representation of an EnOcean dongle."""
-
-    def __init__(self, hass, ser):
-        """Initialize the EnOcean dongle."""
-
-        self.__communicator = SerialCommunicator(port=ser, callback=self.callback)
-        self.__communicator.start()
-        self.hass = hass
-        self.hass.helpers.dispatcher.dispatcher_connect(
-            SIGNAL_SEND_MESSAGE, self._send_message_callback
+    # support for text-based configuration (legacy)
+    if DOMAIN not in config:
+        return True
+
+    if hass.config_entries.async_entries(DOMAIN):
+        # We can only have one dongle. If there is already one in the config,
+        # there is no need to import the yaml based config.
+        return True
+
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
         )
+    )
 
-    def _send_message_callback(self, command):
-        """Send a command through the EnOcean dongle."""
-        self.__communicator.send(command)
-
-    def callback(self, packet):
-        """Handle EnOcean device's callback.
-
-        This is the callback function called by python-enocan whenever there
-        is an incoming packet.
-        """
-
-        if isinstance(packet, RadioPacket):
-            _LOGGER.debug("Received radio packet: %s", packet)
-            self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_RECEIVE_MESSAGE, packet)
-
-
-class EnOceanDevice(Entity):
-    """Parent class for all devices associated with the EnOcean component."""
+    return True
 
-    def __init__(self, dev_id, dev_name="EnOcean device"):
-        """Initialize the device."""
-        self.dev_id = dev_id
-        self.dev_name = dev_name
 
-    async def async_added_to_hass(self):
-        """Register callbacks."""
-        self.async_on_remove(
-            self.hass.helpers.dispatcher.async_dispatcher_connect(
-                SIGNAL_RECEIVE_MESSAGE, self._message_received_callback
-            )
-        )
+async def async_setup_entry(
+    hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
+):
+    """Set up an EnOcean dongle for the given entry."""
+    enocean_data = hass.data.setdefault(DATA_ENOCEAN, {})
+    usb_dongle = EnOceanDongle(hass, config_entry.data[CONF_DEVICE])
+    await usb_dongle.async_setup()
+    enocean_data[ENOCEAN_DONGLE] = usb_dongle
 
-    def _message_received_callback(self, packet):
-        """Handle incoming packets."""
+    return True
 
-        if packet.sender_int == combine_hex(self.dev_id):
-            self.value_changed(packet)
 
-    def value_changed(self, packet):
-        """Update the internal state of the device when a packet arrives."""
+async def async_unload_entry(hass, config_entry):
+    """Unload ENOcean config entry."""
 
-    def send_command(self, data, optional, packet_type):
-        """Send a command via the EnOcean dongle."""
+    enocean_dongle = hass.data[DATA_ENOCEAN][ENOCEAN_DONGLE]
+    enocean_dongle.unload()
+    hass.data.pop(DATA_ENOCEAN)
 
-        packet = Packet(packet_type, data=data, optional=optional)
-        self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_SEND_MESSAGE, packet)
+    return True
diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py
index 7fb8ea5e3f2fa68305e29b544a45808a73128b53..31bd6607ae2a575ec0c9d3b294396ff477c520d4 100644
--- a/homeassistant/components/enocean/binary_sensor.py
+++ b/homeassistant/components/enocean/binary_sensor.py
@@ -3,7 +3,6 @@ import logging
 
 import voluptuous as vol
 
-from homeassistant.components import enocean
 from homeassistant.components.binary_sensor import (
     DEVICE_CLASSES_SCHEMA,
     PLATFORM_SCHEMA,
@@ -12,6 +11,8 @@ from homeassistant.components.binary_sensor import (
 from homeassistant.const import CONF_DEVICE_CLASS, CONF_ID, CONF_NAME
 import homeassistant.helpers.config_validation as cv
 
+from .device import EnOceanEntity
+
 _LOGGER = logging.getLogger(__name__)
 
 DEFAULT_NAME = "EnOcean binary sensor"
@@ -36,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
     add_entities([EnOceanBinarySensor(dev_id, dev_name, device_class)])
 
 
-class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorEntity):
+class EnOceanBinarySensor(EnOceanEntity, BinarySensorEntity):
     """Representation of EnOcean binary sensors such as wall switches.
 
     Supported EEPs (EnOcean Equipment Profiles):
diff --git a/homeassistant/components/enocean/config_flow.py b/homeassistant/components/enocean/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fce66d54e5b290e675d0171bdb32dbfc0459f3f
--- /dev/null
+++ b/homeassistant/components/enocean/config_flow.py
@@ -0,0 +1,94 @@
+"""Config flows for the ENOcean integration."""
+
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.config_entries import CONN_CLASS_ASSUMED
+from homeassistant.const import CONF_DEVICE
+
+from . import dongle
+from .const import DOMAIN  # pylint:disable=unused-import
+from .const import ERROR_INVALID_DONGLE_PATH, LOGGER
+
+
+class EnOceanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
+    """Handle the enOcean config flows."""
+
+    VERSION = 1
+    MANUAL_PATH_VALUE = "Custom path"
+    CONNECTION_CLASS = CONN_CLASS_ASSUMED
+
+    def __init__(self):
+        """Initialize the EnOcean config flow."""
+        self.dongle_path = None
+        self.discovery_info = None
+
+    async def async_step_import(self, data=None):
+        """Import a yaml configuration."""
+
+        if not await self.validate_enocean_conf(data):
+            LOGGER.warning(
+                "Cannot import yaml configuration: %s is not a valid dongle path",
+                data[CONF_DEVICE],
+            )
+            return self.async_abort(reason="invalid_dongle_path")
+
+        return self.create_enocean_entry(data)
+
+    async def async_step_user(self, user_input=None):
+        """Handle an EnOcean config flow start."""
+        if self._async_current_entries():
+            return self.async_abort(reason="single_instance_allowed")
+
+        return await self.async_step_detect()
+
+    async def async_step_detect(self, user_input=None):
+        """Propose a list of detected dongles."""
+        errors = {}
+        if user_input is not None:
+            if user_input[CONF_DEVICE] == self.MANUAL_PATH_VALUE:
+                return await self.async_step_manual(None)
+            if await self.validate_enocean_conf(user_input):
+                return self.create_enocean_entry(user_input)
+            errors = {CONF_DEVICE: ERROR_INVALID_DONGLE_PATH}
+
+        bridges = await self.hass.async_add_executor_job(dongle.detect)
+        if len(bridges) == 0:
+            return await self.async_step_manual(user_input)
+
+        bridges.append(self.MANUAL_PATH_VALUE)
+        return self.async_show_form(
+            step_id="detect",
+            data_schema=vol.Schema({vol.Required(CONF_DEVICE): vol.In(bridges)}),
+            errors=errors,
+        )
+
+    async def async_step_manual(self, user_input=None):
+        """Request manual USB dongle path."""
+        default_value = None
+        errors = {}
+        if user_input is not None:
+            if await self.validate_enocean_conf(user_input):
+                return self.create_enocean_entry(user_input)
+            default_value = user_input[CONF_DEVICE]
+            errors = {CONF_DEVICE: ERROR_INVALID_DONGLE_PATH}
+
+        return self.async_show_form(
+            step_id="manual",
+            data_schema=vol.Schema(
+                {vol.Required(CONF_DEVICE, default=default_value): str}
+            ),
+            errors=errors,
+        )
+
+    async def validate_enocean_conf(self, user_input) -> bool:
+        """Return True if the user_input contains a valid dongle path."""
+        dongle_path = user_input[CONF_DEVICE]
+        path_is_valid = await self.hass.async_add_executor_job(
+            dongle.validate_path, dongle_path
+        )
+        return path_is_valid
+
+    def create_enocean_entry(self, user_input):
+        """Create an entry for the provided configuration."""
+        return self.async_create_entry(title="EnOcean", data=user_input)
diff --git a/homeassistant/components/enocean/const.py b/homeassistant/components/enocean/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..a020a74513784147d91e28813f859749a1c4d96b
--- /dev/null
+++ b/homeassistant/components/enocean/const.py
@@ -0,0 +1,15 @@
+"""Constants for the ENOcean integration."""
+import logging
+
+DOMAIN = "enocean"
+DATA_ENOCEAN = "enocean"
+ENOCEAN_DONGLE = "dongle"
+
+ERROR_INVALID_DONGLE_PATH = "invalid_dongle_path"
+
+SIGNAL_RECEIVE_MESSAGE = "enocean.receive_message"
+SIGNAL_SEND_MESSAGE = "enocean.send_message"
+
+LOGGER = logging.getLogger(__package__)
+
+PLATFORMS = ["light", "binary_sensor", "sensor", "switch"]
diff --git a/homeassistant/components/enocean/device.py b/homeassistant/components/enocean/device.py
new file mode 100644
index 0000000000000000000000000000000000000000..36477d21cffaab63195ebc43b1038259c64bb08c
--- /dev/null
+++ b/homeassistant/components/enocean/device.py
@@ -0,0 +1,39 @@
+"""Representation of an EnOcean device."""
+from enocean.protocol.packet import Packet
+from enocean.utils import combine_hex
+
+from homeassistant.helpers.entity import Entity
+
+from .const import SIGNAL_RECEIVE_MESSAGE, SIGNAL_SEND_MESSAGE
+
+
+class EnOceanEntity(Entity):
+    """Parent class for all entities associated with the EnOcean component."""
+
+    def __init__(self, dev_id, dev_name="EnOcean device"):
+        """Initialize the device."""
+        self.dev_id = dev_id
+        self.dev_name = dev_name
+
+    async def async_added_to_hass(self):
+        """Register callbacks."""
+        self.async_on_remove(
+            self.hass.helpers.dispatcher.async_dispatcher_connect(
+                SIGNAL_RECEIVE_MESSAGE, self._message_received_callback
+            )
+        )
+
+    def _message_received_callback(self, packet):
+        """Handle incoming packets."""
+
+        if packet.sender_int == combine_hex(self.dev_id):
+            self.value_changed(packet)
+
+    def value_changed(self, packet):
+        """Update the internal state of the device when a packet arrives."""
+
+    def send_command(self, data, optional, packet_type):
+        """Send a command via the EnOcean dongle."""
+
+        packet = Packet(packet_type, data=data, optional=optional)
+        self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_SEND_MESSAGE, packet)
diff --git a/homeassistant/components/enocean/dongle.py b/homeassistant/components/enocean/dongle.py
new file mode 100644
index 0000000000000000000000000000000000000000..63ab3e8692563199ea46215fca92a5b041dc7239
--- /dev/null
+++ b/homeassistant/components/enocean/dongle.py
@@ -0,0 +1,87 @@
+"""Representation of an EnOcean dongle."""
+import glob
+import logging
+from os.path import basename, normpath
+
+from enocean.communicators import SerialCommunicator
+from enocean.protocol.packet import RadioPacket
+import serial
+
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+
+from .const import SIGNAL_RECEIVE_MESSAGE, SIGNAL_SEND_MESSAGE
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class EnOceanDongle:
+    """Representation of an EnOcean dongle.
+
+    The dongle is responsible for receiving the ENOcean frames,
+    creating devices if needed, and dispatching messages to platforms.
+    """
+
+    def __init__(self, hass, serial_path):
+        """Initialize the EnOcean dongle."""
+
+        self._communicator = SerialCommunicator(
+            port=serial_path, callback=self.callback
+        )
+        self.serial_path = serial_path
+        self.identifier = basename(normpath(serial_path))
+        self.hass = hass
+        self.dispatcher_disconnect_handle = None
+
+    async def async_setup(self):
+        """Finish the setup of the bridge and supported platforms."""
+        self._communicator.start()
+        self.dispatcher_disconnect_handle = async_dispatcher_connect(
+            self.hass, SIGNAL_SEND_MESSAGE, self._send_message_callback
+        )
+
+    def unload(self):
+        """Disconnect callbacks established at init time."""
+        if self.dispatcher_disconnect_handle:
+            self.dispatcher_disconnect_handle()
+            self.dispatcher_disconnect_handle = None
+
+    def _send_message_callback(self, command):
+        """Send a command through the EnOcean dongle."""
+        self._communicator.send(command)
+
+    def callback(self, packet):
+        """Handle EnOcean device's callback.
+
+        This is the callback function called by python-enocan whenever there
+        is an incoming packet.
+        """
+
+        if isinstance(packet, RadioPacket):
+            _LOGGER.debug("Received radio packet: %s", packet)
+            self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_RECEIVE_MESSAGE, packet)
+
+
+def detect():
+    """Return a list of candidate paths for USB ENOcean dongles.
+
+    This method is currently a bit simplistic, it may need to be
+    improved to support more configurations and OS.
+    """
+    globs_to_test = ["/dev/tty*FTOA2PV*", "/dev/serial/by-id/*EnOcean*"]
+    found_paths = []
+    for current_glob in globs_to_test:
+        found_paths.extend(glob.glob(current_glob))
+
+    return found_paths
+
+
+def validate_path(path: str):
+    """Return True if the provided path points to a valid serial port, False otherwise."""
+    try:
+        # Creating the serial communicator will raise an exception
+        # if it cannot connect
+        SerialCommunicator(port=path)
+        return True
+    except serial.SerialException as exception:
+        _LOGGER.warning("Dongle path %s is invalid: %s", path, str(exception))
+        return False
diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py
index 0df0c94775a6c938045d964e23743a5d5394108d..04b234425c12f83253432a10fc73ba7b2ee70164 100644
--- a/homeassistant/components/enocean/light.py
+++ b/homeassistant/components/enocean/light.py
@@ -4,7 +4,6 @@ import math
 
 import voluptuous as vol
 
-from homeassistant.components import enocean
 from homeassistant.components.light import (
     ATTR_BRIGHTNESS,
     PLATFORM_SCHEMA,
@@ -14,6 +13,8 @@ from homeassistant.components.light import (
 from homeassistant.const import CONF_ID, CONF_NAME
 import homeassistant.helpers.config_validation as cv
 
+from .device import EnOceanEntity
+
 _LOGGER = logging.getLogger(__name__)
 
 CONF_SENDER_ID = "sender_id"
@@ -39,7 +40,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
     add_entities([EnOceanLight(sender_id, dev_id, dev_name)])
 
 
-class EnOceanLight(enocean.EnOceanDevice, LightEntity):
+class EnOceanLight(EnOceanEntity, LightEntity):
     """Representation of an EnOcean light source."""
 
     def __init__(self, sender_id, dev_id, dev_name):
diff --git a/homeassistant/components/enocean/manifest.json b/homeassistant/components/enocean/manifest.json
index a02661f8883eb449e080bd40d1c66c9ee3bef7c2..390b48342fde8d13c4f7afb981c91be295696396 100644
--- a/homeassistant/components/enocean/manifest.json
+++ b/homeassistant/components/enocean/manifest.json
@@ -2,6 +2,11 @@
   "domain": "enocean",
   "name": "EnOcean",
   "documentation": "https://www.home-assistant.io/integrations/enocean",
-  "requirements": ["enocean==0.50"],
-  "codeowners": ["@bdurrer"]
+  "requirements": [
+    "enocean==0.50"
+  ],
+  "codeowners": [
+    "@bdurrer"
+  ],
+  "config_flow": true
 }
diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py
index 16f6238acdc45de47114db65a74b685038820fec..07d06824365aeda7ebb5ba89f8e1135b3d796de5 100644
--- a/homeassistant/components/enocean/sensor.py
+++ b/homeassistant/components/enocean/sensor.py
@@ -3,7 +3,6 @@ import logging
 
 import voluptuous as vol
 
-from homeassistant.components import enocean
 from homeassistant.components.sensor import PLATFORM_SCHEMA
 from homeassistant.const import (
     CONF_DEVICE_CLASS,
@@ -21,6 +20,8 @@ from homeassistant.const import (
 import homeassistant.helpers.config_validation as cv
 from homeassistant.helpers.restore_state import RestoreEntity
 
+from .device import EnOceanEntity
+
 _LOGGER = logging.getLogger(__name__)
 
 CONF_MAX_TEMP = "max_temp"
@@ -62,7 +63,6 @@ SENSOR_TYPES = {
     },
 }
 
-
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
     {
         vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
@@ -105,7 +105,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
         add_entities([EnOceanWindowHandle(dev_id, dev_name)])
 
 
-class EnOceanSensor(enocean.EnOceanDevice, RestoreEntity):
+class EnOceanSensor(EnOceanEntity, RestoreEntity):
     """Representation of an  EnOcean sensor device such as a power meter."""
 
     def __init__(self, dev_id, dev_name, sensor_type):
diff --git a/homeassistant/components/enocean/strings.json b/homeassistant/components/enocean/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..633c97e51af4a4ff537d10fb44b5bf6a7f718402
--- /dev/null
+++ b/homeassistant/components/enocean/strings.json
@@ -0,0 +1,27 @@
+{
+  "title": "EnOcean",
+  "config": {
+    "flow_title": "ENOcean setup",
+    "step": {
+      "detect": {
+        "title": "Select the path to you ENOcean dongle",
+        "data": {
+          "path": "USB dongle path"
+        }
+      },
+      "manual": {
+        "title": "Enter the path to you ENOcean dongle",
+        "data": {
+          "path": "USB dongle path"
+        }
+      }
+    },
+    "error": {
+      "invalid_dongle_path": "No valid dongle found for this path"
+    },
+    "abort": {
+      "invalid_dongle_path": "Invalid dongle path",
+      "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
+    }
+  }
+}
diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py
index 92642e329d93d6518ebf7422572444ac8dd981ff..6ce5fbd31806994676aad9e66e66839a75b00370 100644
--- a/homeassistant/components/enocean/switch.py
+++ b/homeassistant/components/enocean/switch.py
@@ -3,12 +3,13 @@ import logging
 
 import voluptuous as vol
 
-from homeassistant.components import enocean
 from homeassistant.components.switch import PLATFORM_SCHEMA
 from homeassistant.const import CONF_ID, CONF_NAME
 import homeassistant.helpers.config_validation as cv
 from homeassistant.helpers.entity import ToggleEntity
 
+from .device import EnOceanEntity
+
 _LOGGER = logging.getLogger(__name__)
 
 CONF_CHANNEL = "channel"
@@ -32,7 +33,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
     add_entities([EnOceanSwitch(dev_id, dev_name, channel)])
 
 
-class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity):
+class EnOceanSwitch(EnOceanEntity, ToggleEntity):
     """Representation of an EnOcean switch device."""
 
     def __init__(self, dev_id, dev_name, channel):
diff --git a/homeassistant/components/enocean/translations/en.json b/homeassistant/components/enocean/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..7715775335ec7098133c379d4c535d0a3078fe05
--- /dev/null
+++ b/homeassistant/components/enocean/translations/en.json
@@ -0,0 +1,27 @@
+{
+  "title": "EnOcean",
+  "config": {
+    "flow_title": "ENOcean setup",
+    "step": {
+      "detect": {
+        "title": "Select the path to you ENOcean dongle",
+        "data": {
+          "path": "USB dongle path"
+        }
+      },
+      "manual": {
+        "title": "Enter the path to you ENOcean dongle",
+        "data": {
+          "path": "USB dongle path"
+        }
+      }
+    },
+    "error": {
+      "invalid_dongle_path": "No valid dongle found for this path"
+    },
+    "abort": {
+      "invalid_dongle_path": "Invalid dongle path",
+      "single_instance_allowed": "An instance is already configured"
+    }
+  }
+}
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 9e2386d6bbaff98645d9348d260b0b5d3cdebace..23fdc656af6176038456c503528776228940b853 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -45,6 +45,7 @@ FLOWS = [
     "elgato",
     "elkm1",
     "emulated_roku",
+    "enocean",
     "esphome",
     "flick_electric",
     "flume",
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 0ecec6a12b70d0733b954764f9296758468b40e6..6f6be87b793c4ed7e6255421625ed5ec5a4e11e4 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -265,6 +265,9 @@ emoji==0.5.4
 # homeassistant.components.emulated_roku
 emulated_roku==0.2.1
 
+# homeassistant.components.enocean
+enocean==0.50
+
 # homeassistant.components.season
 ephem==3.7.7.0
 
diff --git a/tests/components/enocean/__init__.py b/tests/components/enocean/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..81771694abf8bc7804c3318458903b84958da2b1
--- /dev/null
+++ b/tests/components/enocean/__init__.py
@@ -0,0 +1 @@
+"""Tests of the EnOcean integration."""
diff --git a/tests/components/enocean/test_config_flow.py b/tests/components/enocean/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..aee6765272e302f6d3d9fe9001a62b5d136a51c2
--- /dev/null
+++ b/tests/components/enocean/test_config_flow.py
@@ -0,0 +1,159 @@
+"""Tests for EnOcean config flow."""
+from homeassistant import data_entry_flow
+from homeassistant.components.enocean.config_flow import EnOceanFlowHandler
+from homeassistant.components.enocean.const import DOMAIN
+from homeassistant.const import CONF_DEVICE
+
+from tests.async_mock import Mock, patch
+from tests.common import MockConfigEntry
+
+DONGLE_VALIDATE_PATH_METHOD = "homeassistant.components.enocean.dongle.validate_path"
+DONGLE_DETECT_METHOD = "homeassistant.components.enocean.dongle.detect"
+
+
+async def test_user_flow_cannot_create_multiple_instances(hass):
+    """Test that the user flow aborts if an instance is already configured."""
+    entry = MockConfigEntry(
+        domain=DOMAIN, data={CONF_DEVICE: "/already/configured/path"}
+    )
+    entry.add_to_hass(hass)
+
+    with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "user"}
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
+    assert result["reason"] == "single_instance_allowed"
+
+
+async def test_user_flow_with_detected_dongle(hass):
+    """Test the user flow with a detected ENOcean dongle."""
+    FAKE_DONGLE_PATH = "/fake/dongle"
+
+    with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "user"}
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "detect"
+    devices = result["data_schema"].schema.get("device").container
+    assert FAKE_DONGLE_PATH in devices
+    assert EnOceanFlowHandler.MANUAL_PATH_VALUE in devices
+
+
+async def test_user_flow_with_no_detected_dongle(hass):
+    """Test the user flow with a detected ENOcean dongle."""
+    with patch(DONGLE_DETECT_METHOD, Mock(return_value=[])):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "user"}
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "manual"
+
+
+async def test_detection_flow_with_valid_path(hass):
+    """Test the detection flow with a valid path selected."""
+    USER_PROVIDED_PATH = "/user/provided/path"
+
+    with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "detect"}, data={CONF_DEVICE: USER_PROVIDED_PATH}
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+    assert result["data"][CONF_DEVICE] == USER_PROVIDED_PATH
+
+
+async def test_detection_flow_with_custom_path(hass):
+    """Test the detection flow with custom path selected."""
+    USER_PROVIDED_PATH = EnOceanFlowHandler.MANUAL_PATH_VALUE
+    FAKE_DONGLE_PATH = "/fake/dongle"
+
+    with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)):
+        with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])):
+            result = await hass.config_entries.flow.async_init(
+                DOMAIN,
+                context={"source": "detect"},
+                data={CONF_DEVICE: USER_PROVIDED_PATH},
+            )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "manual"
+
+
+async def test_detection_flow_with_invalid_path(hass):
+    """Test the detection flow with an invalid path selected."""
+    USER_PROVIDED_PATH = "/invalid/path"
+    FAKE_DONGLE_PATH = "/fake/dongle"
+
+    with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)):
+        with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])):
+            result = await hass.config_entries.flow.async_init(
+                DOMAIN,
+                context={"source": "detect"},
+                data={CONF_DEVICE: USER_PROVIDED_PATH},
+            )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "detect"
+    assert CONF_DEVICE in result["errors"]
+
+
+async def test_manual_flow_with_valid_path(hass):
+    """Test the manual flow with a valid path."""
+    USER_PROVIDED_PATH = "/user/provided/path"
+
+    with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "manual"}, data={CONF_DEVICE: USER_PROVIDED_PATH}
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+    assert result["data"][CONF_DEVICE] == USER_PROVIDED_PATH
+
+
+async def test_manual_flow_with_invalid_path(hass):
+    """Test the manual flow with an invalid path."""
+    USER_PROVIDED_PATH = "/user/provided/path"
+
+    with patch(
+        DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False),
+    ):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "manual"}, data={CONF_DEVICE: USER_PROVIDED_PATH}
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "manual"
+    assert CONF_DEVICE in result["errors"]
+
+
+async def test_import_flow_with_valid_path(hass):
+    """Test the import flow with a valid path."""
+    DATA_TO_IMPORT = {CONF_DEVICE: "/valid/path/to/import"}
+
+    with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "import"}, data=DATA_TO_IMPORT
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+    assert result["data"][CONF_DEVICE] == DATA_TO_IMPORT[CONF_DEVICE]
+
+
+async def test_import_flow_with_invalid_path(hass):
+    """Test the import flow with an invalid path."""
+    DATA_TO_IMPORT = {CONF_DEVICE: "/invalid/path/to/import"}
+
+    with patch(
+        DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False),
+    ):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": "import"}, data=DATA_TO_IMPORT
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
+    assert result["reason"] == "invalid_dongle_path"