diff --git a/.coveragerc b/.coveragerc
index a5ff56b8d0419bb8a6d58fe87a001bf83ac6eef2..6d4c8a667623d2d16b5105216c8c8b2edef4f7d1 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -601,6 +601,9 @@ omit =
     homeassistant/components/oasa_telematics/sensor.py
     homeassistant/components/ohmconnect/sensor.py
     homeassistant/components/ombi/*
+    homeassistant/components/omnilogic/__init__.py
+    homeassistant/components/omnilogic/common.py
+    homeassistant/components/omnilogic/sensor.py
     homeassistant/components/onewire/sensor.py
     homeassistant/components/onkyo/media_player.py
     homeassistant/components/onvif/__init__.py
diff --git a/CODEOWNERS b/CODEOWNERS
index 0f07edf16ef02810a244fc179a2f42baf0241bb4..05c3dcf50874fc3156c6dd2c4efbb73ef9f0518b 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -301,6 +301,7 @@ homeassistant/components/nzbget/* @chriscla
 homeassistant/components/obihai/* @dshokouhi
 homeassistant/components/ohmconnect/* @robbiet480
 homeassistant/components/ombi/* @larssont
+homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
 homeassistant/components/onboarding/* @home-assistant/core
 homeassistant/components/onewire/* @garbled1
 homeassistant/components/onvif/* @hunterjm
diff --git a/homeassistant/components/omnilogic/__init__.py b/homeassistant/components/omnilogic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff4dd93a0e1537c05e8943d3cf2874a9411e2488
--- /dev/null
+++ b/homeassistant/components/omnilogic/__init__.py
@@ -0,0 +1,90 @@
+"""The Omnilogic integration."""
+import asyncio
+import logging
+
+from omnilogic import LoginException, OmniLogic, OmniLogicException
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
+from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.helpers import aiohttp_client
+
+from .common import OmniLogicUpdateCoordinator
+from .const import CONF_SCAN_INTERVAL, COORDINATOR, DOMAIN, OMNI_API
+
+_LOGGER = logging.getLogger(__name__)
+
+PLATFORMS = ["sensor"]
+
+
+async def async_setup(hass: HomeAssistant, config: dict):
+    """Set up the Omnilogic component."""
+    hass.data.setdefault(DOMAIN, {})
+
+    return True
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Set up Omnilogic from a config entry."""
+
+    conf = entry.data
+    username = conf[CONF_USERNAME]
+    password = conf[CONF_PASSWORD]
+
+    polling_interval = 6
+    if CONF_SCAN_INTERVAL in conf:
+        polling_interval = conf[CONF_SCAN_INTERVAL]
+
+    session = aiohttp_client.async_get_clientsession(hass)
+
+    api = OmniLogic(username, password, session)
+
+    try:
+        await api.connect()
+        await api.get_telemetry_data()
+    except LoginException as error:
+        _LOGGER.error("Login Failed: %s", error)
+        return False
+    except OmniLogicException as error:
+        _LOGGER.debug("OmniLogic API error: %s", error)
+        raise ConfigEntryNotReady from error
+
+    coordinator = OmniLogicUpdateCoordinator(
+        hass=hass,
+        api=api,
+        name="Omnilogic",
+        polling_interval=polling_interval,
+    )
+    await coordinator.async_refresh()
+
+    if not coordinator.last_update_success:
+        raise ConfigEntryNotReady
+
+    hass.data[DOMAIN][entry.entry_id] = {
+        COORDINATOR: coordinator,
+        OMNI_API: api,
+    }
+
+    for component in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(entry, component)
+        )
+
+    return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(entry, component)
+                for component in PLATFORMS
+            ]
+        )
+    )
+    if unload_ok:
+        hass.data[DOMAIN].pop(entry.entry_id)
+
+    return unload_ok
diff --git a/homeassistant/components/omnilogic/common.py b/homeassistant/components/omnilogic/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..791d81b6757a53c993bf3599baab63b3355129e8
--- /dev/null
+++ b/homeassistant/components/omnilogic/common.py
@@ -0,0 +1,157 @@
+"""Common classes and elements for Omnilogic Integration."""
+
+from datetime import timedelta
+import logging
+
+from omnilogic import OmniLogicException
+
+from homeassistant.const import ATTR_NAME
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.update_coordinator import (
+    CoordinatorEntity,
+    DataUpdateCoordinator,
+    UpdateFailed,
+)
+
+from .const import (
+    ALL_ITEM_KINDS,
+    ATTR_IDENTIFIERS,
+    ATTR_MANUFACTURER,
+    ATTR_MODEL,
+    DOMAIN,
+)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class OmniLogicUpdateCoordinator(DataUpdateCoordinator):
+    """Class to manage fetching update data from single endpoint."""
+
+    def __init__(
+        self,
+        hass: HomeAssistant,
+        api: str,
+        name: str,
+        polling_interval: int,
+    ):
+        """Initialize the global Omnilogic data updater."""
+        self.api = api
+
+        super().__init__(
+            hass=hass,
+            logger=_LOGGER,
+            name=name,
+            update_interval=timedelta(seconds=polling_interval),
+        )
+
+    async def _async_update_data(self):
+        """Fetch data from OmniLogic."""
+        try:
+            data = await self.api.get_telemetry_data()
+
+        except OmniLogicException as error:
+            raise UpdateFailed(f"Error updating from OmniLogic: {error}") from error
+
+        parsed_data = {}
+
+        def get_item_data(item, item_kind, current_id, data):
+            """Get data per kind of Omnilogic API item."""
+            if isinstance(item, list):
+                for single_item in item:
+                    data = get_item_data(single_item, item_kind, current_id, data)
+
+            if "systemId" in item:
+                system_id = item["systemId"]
+                current_id = current_id + (item_kind, system_id)
+                data[current_id] = item
+
+            for kind in ALL_ITEM_KINDS:
+                if kind in item:
+                    data = get_item_data(item[kind], kind, current_id, data)
+
+            return data
+
+        parsed_data = get_item_data(data, "Backyard", (), parsed_data)
+
+        return parsed_data
+
+
+class OmniLogicEntity(CoordinatorEntity):
+    """Defines the base OmniLogic entity."""
+
+    def __init__(
+        self,
+        coordinator: OmniLogicUpdateCoordinator,
+        kind: str,
+        name: str,
+        item_id: tuple,
+        icon: str,
+    ):
+        """Initialize the OmniLogic Entity."""
+        super().__init__(coordinator)
+
+        bow_id = None
+        entity_data = coordinator.data[item_id]
+
+        backyard_id = item_id[:2]
+        if len(item_id) == 6:
+            bow_id = item_id[:4]
+
+        msp_system_id = coordinator.data[backyard_id]["systemId"]
+        entity_friendly_name = f"{coordinator.data[backyard_id]['BackyardName']} "
+        unique_id = f"{msp_system_id}"
+
+        if bow_id is not None:
+            unique_id = f"{unique_id}_{coordinator.data[bow_id]['systemId']}"
+            entity_friendly_name = (
+                f"{entity_friendly_name}{coordinator.data[bow_id]['Name']} "
+            )
+
+        unique_id = f"{unique_id}_{coordinator.data[item_id]['systemId']}_{kind}"
+
+        if entity_data.get("Name") is not None:
+            entity_friendly_name = f"{entity_friendly_name} {entity_data['Name']}"
+
+        entity_friendly_name = f"{entity_friendly_name} {name}"
+
+        unique_id = unique_id.replace(" ", "_")
+
+        self._kind = kind
+        self._name = entity_friendly_name
+        self._unique_id = unique_id
+        self._item_id = item_id
+        self._icon = icon
+        self._attrs = {}
+        self._msp_system_id = msp_system_id
+        self._backyard_name = coordinator.data[backyard_id]["BackyardName"]
+
+    @property
+    def unique_id(self) -> str:
+        """Return a unique, Home Assistant friendly identifier for this entity."""
+        return self._unique_id
+
+    @property
+    def name(self) -> str:
+        """Return the name of the entity."""
+        return self._name
+
+    @property
+    def icon(self):
+        """Return the icon for the entity."""
+        return self._icon
+
+    @property
+    def device_state_attributes(self):
+        """Return the attributes."""
+        return self._attrs
+
+    @property
+    def device_info(self):
+        """Define the device as back yard/MSP System."""
+
+        return {
+            ATTR_IDENTIFIERS: {(DOMAIN, self._msp_system_id)},
+            ATTR_NAME: self._backyard_name,
+            ATTR_MANUFACTURER: "Hayward",
+            ATTR_MODEL: "OmniLogic",
+        }
diff --git a/homeassistant/components/omnilogic/config_flow.py b/homeassistant/components/omnilogic/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..641ec5a8d94bfd2dcb2de3e49c44713456e857c2
--- /dev/null
+++ b/homeassistant/components/omnilogic/config_flow.py
@@ -0,0 +1,95 @@
+"""Config flow for Omnilogic integration."""
+import logging
+
+from omnilogic import LoginException, OmniLogic, OmniLogicException
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
+from homeassistant.core import callback
+from homeassistant.helpers import aiohttp_client
+
+from .const import CONF_SCAN_INTERVAL, DOMAIN  # pylint:disable=unused-import
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for Omnilogic."""
+
+    VERSION = 1
+    CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
+
+    @staticmethod
+    @callback
+    def async_get_options_flow(config_entry):
+        """Get the options flow for this handler."""
+        return OptionsFlowHandler(config_entry)
+
+    async def async_step_user(self, user_input=None):
+        """Handle the initial step."""
+        errors = {}
+
+        config_entry = self.hass.config_entries.async_entries(DOMAIN)
+        if config_entry:
+            return self.async_abort(reason="single_instance_allowed")
+
+        errors = {}
+
+        if user_input is not None:
+            username = user_input[CONF_USERNAME]
+            password = user_input[CONF_PASSWORD]
+
+            session = aiohttp_client.async_get_clientsession(self.hass)
+            omni = OmniLogic(username, password, session)
+
+            try:
+                await omni.connect()
+            except LoginException:
+                errors["base"] = "invalid_auth"
+            except OmniLogicException:
+                errors["base"] = "cannot_connect"
+            except Exception:  # pylint: disable=broad-except
+                _LOGGER.exception("Unexpected exception")
+                errors["base"] = "unknown"
+            else:
+                await self.async_set_unique_id(user_input["username"])
+                self._abort_if_unique_id_configured()
+                return self.async_create_entry(title="Omnilogic", data=user_input)
+
+        return self.async_show_form(
+            step_id="user",
+            data_schema=vol.Schema(
+                {
+                    vol.Required(CONF_USERNAME): str,
+                    vol.Required(CONF_PASSWORD): str,
+                }
+            ),
+            errors=errors,
+        )
+
+
+class OptionsFlowHandler(config_entries.OptionsFlow):
+    """Handle Omnilogic client options."""
+
+    def __init__(self, config_entry):
+        """Initialize options flow."""
+        self.config_entry = config_entry
+
+    async def async_step_init(self, user_input=None):
+        """Manage options."""
+
+        if user_input is not None:
+            return self.async_create_entry(title="", data=user_input)
+
+        return self.async_show_form(
+            step_id="init",
+            data_schema=vol.Schema(
+                {
+                    vol.Optional(
+                        CONF_SCAN_INTERVAL,
+                        default=6,
+                    ): int,
+                }
+            ),
+        )
diff --git a/homeassistant/components/omnilogic/const.py b/homeassistant/components/omnilogic/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..a57ef2b062a9281a8f899ffd850d3afd47e586ff
--- /dev/null
+++ b/homeassistant/components/omnilogic/const.py
@@ -0,0 +1,29 @@
+"""Constants for the Omnilogic integration."""
+
+DOMAIN = "omnilogic"
+CONF_SCAN_INTERVAL = "polling_interval"
+COORDINATOR = "coordinator"
+OMNI_API = "omni_api"
+ATTR_IDENTIFIERS = "identifiers"
+ATTR_MANUFACTURER = "manufacturer"
+ATTR_MODEL = "model"
+
+PUMP_TYPES = {
+    "FMT_VARIABLE_SPEED_PUMP": "VARIABLE",
+    "FMT_SINGLE_SPEED": "SINGLE",
+    "FMT_DUAL_SPEED": "DUAL",
+    "PMP_VARIABLE_SPEED_PUMP": "VARIABLE",
+    "PMP_SINGLE_SPEED": "SINGLE",
+    "PMP_DUAL_SPEED": "DUAL",
+}
+
+ALL_ITEM_KINDS = {
+    "BOWS",
+    "Filter",
+    "Heater",
+    "Chlorinator",
+    "CSAD",
+    "Lights",
+    "Relays",
+    "Pumps",
+}
diff --git a/homeassistant/components/omnilogic/manifest.json b/homeassistant/components/omnilogic/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..468b48d620a9d6693369006ce78b662947737dbb
--- /dev/null
+++ b/homeassistant/components/omnilogic/manifest.json
@@ -0,0 +1,8 @@
+{
+  "domain": "omnilogic",
+  "name": "Hayward Omnilogic",
+  "config_flow": true,
+  "documentation": "https://www.home-assistant.io/integrations/omnilogic",
+  "requirements": ["omnilogic==0.4.0"],
+  "codeowners": ["@oliver84","@djtimca","@gentoosu"]
+}
diff --git a/homeassistant/components/omnilogic/sensor.py b/homeassistant/components/omnilogic/sensor.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4bb0f45d5ed8c187ef120dc5b70e16cb6e6fc26
--- /dev/null
+++ b/homeassistant/components/omnilogic/sensor.py
@@ -0,0 +1,356 @@
+"""Definition and setup of the Omnilogic Sensors for Home Assistant."""
+
+import logging
+
+from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE
+from homeassistant.const import (
+    CONCENTRATION_PARTS_PER_MILLION,
+    MASS_GRAMS,
+    PERCENTAGE,
+    TEMP_CELSIUS,
+    TEMP_FAHRENHEIT,
+    VOLUME_LITERS,
+)
+
+from .common import OmniLogicEntity, OmniLogicUpdateCoordinator
+from .const import COORDINATOR, DOMAIN, PUMP_TYPES
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(hass, entry, async_add_entities):
+    """Set up the sensor platform."""
+
+    coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]
+    entities = []
+
+    for item_id, item in coordinator.data.items():
+        id_len = len(item_id)
+        item_kind = item_id[-2]
+        entity_settings = SENSOR_TYPES.get((id_len, item_kind))
+
+        if not entity_settings:
+            continue
+
+        for entity_setting in entity_settings:
+            for state_key, entity_class in entity_setting["entity_classes"].items():
+                if state_key not in item:
+                    continue
+
+                guard = False
+                for guard_condition in entity_setting["guard_condition"]:
+                    if guard_condition and all(
+                        item.get(guard_key) == guard_value
+                        for guard_key, guard_value in guard_condition.items()
+                    ):
+                        guard = True
+
+                if guard:
+                    continue
+
+                entity = entity_class(
+                    coordinator=coordinator,
+                    state_key=state_key,
+                    name=entity_setting["name"],
+                    kind=entity_setting["kind"],
+                    item_id=item_id,
+                    device_class=entity_setting["device_class"],
+                    icon=entity_setting["icon"],
+                    unit=entity_setting["unit"],
+                )
+
+                entities.append(entity)
+
+    async_add_entities(entities)
+
+
+class OmnilogicSensor(OmniLogicEntity):
+    """Defines an Omnilogic sensor entity."""
+
+    def __init__(
+        self,
+        coordinator: OmniLogicUpdateCoordinator,
+        kind: str,
+        name: str,
+        device_class: str,
+        icon: str,
+        unit: str,
+        item_id: tuple,
+        state_key: str,
+    ):
+        """Initialize Entities."""
+        super().__init__(
+            coordinator=coordinator,
+            kind=kind,
+            name=name,
+            item_id=item_id,
+            icon=icon,
+        )
+
+        backyard_id = item_id[:2]
+        unit_type = coordinator.data[backyard_id].get("Unit-of-Measurement")
+
+        self._unit_type = unit_type
+        self._device_class = device_class
+        self._unit = unit
+        self._state_key = state_key
+
+    @property
+    def device_class(self):
+        """Return the device class of the entity."""
+        return self._device_class
+
+    @property
+    def unit_of_measurement(self):
+        """Return the right unit of measure."""
+        return self._unit
+
+
+class OmniLogicTemperatureSensor(OmnilogicSensor):
+    """Define an OmniLogic Temperature (Air/Water) Sensor."""
+
+    @property
+    def state(self):
+        """Return the state for the temperature sensor."""
+        sensor_data = self.coordinator.data[self._item_id][self._state_key]
+
+        hayward_state = sensor_data
+        hayward_unit_of_measure = TEMP_FAHRENHEIT
+        state = sensor_data
+
+        if self._unit_type == "Metric":
+            hayward_state = round((hayward_state - 32) * 5 / 9, 1)
+            hayward_unit_of_measure = TEMP_CELSIUS
+
+        if int(sensor_data) == -1:
+            hayward_state = None
+            state = None
+
+        self._attrs["hayward_temperature"] = hayward_state
+        self._attrs["hayward_unit_of_measure"] = hayward_unit_of_measure
+
+        self._unit = TEMP_FAHRENHEIT
+
+        return state
+
+
+class OmniLogicPumpSpeedSensor(OmnilogicSensor):
+    """Define an OmniLogic Pump Speed Sensor."""
+
+    @property
+    def state(self):
+        """Return the state for the pump speed sensor."""
+
+        pump_type = PUMP_TYPES[self.coordinator.data[self._item_id]["Filter-Type"]]
+        pump_speed = self.coordinator.data[self._item_id][self._state_key]
+
+        if pump_type == "VARIABLE":
+            self._unit = PERCENTAGE
+            state = pump_speed
+        elif pump_type == "DUAL":
+            if pump_speed == 0:
+                state = "off"
+            elif pump_speed == self.coordinator.data[self._item_id].get(
+                "Min-Pump-Speed"
+            ):
+                state = "low"
+            elif pump_speed == self.coordinator.data[self._item_id].get(
+                "Max-Pump-Speed"
+            ):
+                state = "high"
+
+        self._attrs["pump_type"] = pump_type
+
+        return state
+
+
+class OmniLogicSaltLevelSensor(OmnilogicSensor):
+    """Define an OmniLogic Salt Level Sensor."""
+
+    @property
+    def state(self):
+        """Return the state for the salt level sensor."""
+
+        salt_return = self.coordinator.data[self._item_id][self._state_key]
+        unit_of_measurement = self._unit
+
+        if self._unit_type == "Metric":
+            salt_return = round(salt_return / 1000, 2)
+            unit_of_measurement = f"{MASS_GRAMS}/{VOLUME_LITERS}"
+
+        self._unit = unit_of_measurement
+
+        return salt_return
+
+
+class OmniLogicChlorinatorSensor(OmnilogicSensor):
+    """Define an OmniLogic Chlorinator Sensor."""
+
+    @property
+    def state(self):
+        """Return the state for the chlorinator sensor."""
+        state = self.coordinator.data[self._item_id][self._state_key]
+
+        return state
+
+
+class OmniLogicPHSensor(OmnilogicSensor):
+    """Define an OmniLogic pH Sensor."""
+
+    @property
+    def state(self):
+        """Return the state for the pH sensor."""
+
+        ph_state = self.coordinator.data[self._item_id][self._state_key]
+
+        if ph_state == 0:
+            ph_state = None
+
+        return ph_state
+
+
+class OmniLogicORPSensor(OmnilogicSensor):
+    """Define an OmniLogic ORP Sensor."""
+
+    def __init__(
+        self,
+        coordinator: OmniLogicUpdateCoordinator,
+        state_key: str,
+        name: str,
+        kind: str,
+        item_id: tuple,
+        device_class: str,
+        icon: str,
+        unit: str,
+    ):
+        """Initialize the sensor."""
+        super().__init__(
+            coordinator=coordinator,
+            kind=kind,
+            name=name,
+            device_class=device_class,
+            icon=icon,
+            unit=unit,
+            item_id=item_id,
+            state_key=state_key,
+        )
+
+    @property
+    def state(self):
+        """Return the state for the ORP sensor."""
+
+        orp_state = self.coordinator.data[self._item_id][self._state_key]
+
+        if orp_state == -1:
+            orp_state = None
+
+        return orp_state
+
+
+SENSOR_TYPES = {
+    (2, "Backyard"): [
+        {
+            "entity_classes": {"airTemp": OmniLogicTemperatureSensor},
+            "name": "Air Temperature",
+            "kind": "air_temperature",
+            "device_class": DEVICE_CLASS_TEMPERATURE,
+            "icon": None,
+            "unit": TEMP_FAHRENHEIT,
+            "guard_condition": [{}],
+        },
+    ],
+    (4, "BOWS"): [
+        {
+            "entity_classes": {"waterTemp": OmniLogicTemperatureSensor},
+            "name": "Water Temperature",
+            "kind": "water_temperature",
+            "device_class": DEVICE_CLASS_TEMPERATURE,
+            "icon": None,
+            "unit": TEMP_FAHRENHEIT,
+            "guard_condition": [{}],
+        },
+    ],
+    (6, "Filter"): [
+        {
+            "entity_classes": {"filterSpeed": OmniLogicPumpSpeedSensor},
+            "name": "Speed",
+            "kind": "filter_pump_speed",
+            "device_class": None,
+            "icon": "mdi:speedometer",
+            "unit": PERCENTAGE,
+            "guard_condition": [
+                {"Type": "FMT_SINGLE_SPEED"},
+            ],
+        },
+    ],
+    (6, "Pumps"): [
+        {
+            "entity_classes": {"pumpSpeed": OmniLogicPumpSpeedSensor},
+            "name": "Pump Speed",
+            "kind": "pump_speed",
+            "device_class": None,
+            "icon": "mdi:speedometer",
+            "unit": PERCENTAGE,
+            "guard_condition": [
+                {"Type": "PMP_SINGLE_SPEED"},
+            ],
+        },
+    ],
+    (6, "Chlorinator"): [
+        {
+            "entity_classes": {"Timed-Percent": OmniLogicChlorinatorSensor},
+            "name": "Setting",
+            "kind": "chlorinator",
+            "device_class": None,
+            "icon": "mdi:gauge",
+            "unit": PERCENTAGE,
+            "guard_condition": [
+                {
+                    "Shared-Type": "BOW_SHARED_EQUIPMENT",
+                    "status": "0",
+                },
+                {
+                    "operatingMode": "2",
+                },
+            ],
+        },
+        {
+            "entity_classes": {"avgSaltLevel": OmniLogicSaltLevelSensor},
+            "name": "Salt Level",
+            "kind": "salt_level",
+            "device_class": None,
+            "icon": "mdi:gauge",
+            "unit": CONCENTRATION_PARTS_PER_MILLION,
+            "guard_condition": [
+                {
+                    "Shared-Type": "BOW_SHARED_EQUIPMENT",
+                    "status": "0",
+                },
+            ],
+        },
+    ],
+    (6, "CSAD"): [
+        {
+            "entity_classes": {"ph": OmniLogicPHSensor},
+            "name": "pH",
+            "kind": "csad_ph",
+            "device_class": None,
+            "icon": "mdi:gauge",
+            "unit": "pH",
+            "guard_condition": [
+                {"ph": ""},
+            ],
+        },
+        {
+            "entity_classes": {"orp": OmniLogicORPSensor},
+            "name": "ORP",
+            "kind": "csad_orp",
+            "device_class": None,
+            "icon": "mdi:gauge",
+            "unit": "mV",
+            "guard_condition": [
+                {"orp": ""},
+            ],
+        },
+    ],
+}
diff --git a/homeassistant/components/omnilogic/strings.json b/homeassistant/components/omnilogic/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..285bc29b802c9e783dea9dbded71e196d96856ef
--- /dev/null
+++ b/homeassistant/components/omnilogic/strings.json
@@ -0,0 +1,30 @@
+{
+  "title": "Omnilogic",
+  "config": {
+    "step": {
+      "user": {
+        "data": {
+          "username": "[%key:common::config_flow::data::username%]",
+          "password": "[%key:common::config_flow::data::password%]"
+        }
+      }
+    },
+    "error": {
+      "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
+      "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
+      "unknown": "[%key:common::config_flow::error::unknown%]"
+    },
+    "abort": {
+      "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
+    }
+  },
+  "options": {
+    "step": {
+      "init": {
+        "data": {
+          "polling_interval": "Polling interval (in seconds)"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/homeassistant/components/omnilogic/translations/en.json b/homeassistant/components/omnilogic/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..8dde40be8fc017494e959c9b5772127090cf44ac
--- /dev/null
+++ b/homeassistant/components/omnilogic/translations/en.json
@@ -0,0 +1,30 @@
+{
+    "config": {
+        "abort": {
+            "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
+        },
+        "error": {
+            "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
+            "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
+            "unknown": "[%key:common::config_flow::error::unknown%]"
+        },
+        "step": {
+            "user": {
+                "data": {
+                    "password": "[%key:common::config_flow::data::password%]",
+                    "username": "[%key:common::config_flow::data::username%]"
+                }
+            }
+        }
+    },
+    "options": {
+        "step": {
+            "init": {
+                "data": {
+                    "polling_interval": "Polling interval (in seconds)"
+                }
+            }
+        }
+    },
+    "title": "Omnilogic"
+}
\ No newline at end of file
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index fae053ac1a14ef2cdd480cc7adf2adf6ca011dd7..55e6bf2eafea7ee93dc206ab4fbedac30f4ac6c9 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -127,6 +127,7 @@ FLOWS = [
     "nut",
     "nws",
     "nzbget",
+    "omnilogic",
     "onvif",
     "opentherm_gw",
     "openuv",
diff --git a/requirements_all.txt b/requirements_all.txt
index 7f3c6f2c012f307064f40bc9c40517eabac11407..0032fa2fdc383370a830bd0817012bbad7a4a098 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1012,6 +1012,9 @@ oauth2client==4.0.0
 # homeassistant.components.oem
 oemthermostat==1.1
 
+# homeassistant.components.omnilogic
+omnilogic==0.4.0
+
 # homeassistant.components.onkyo
 onkyo-eiscp==1.2.7
 
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index fb596cf04e6a3fb6ec82e6c8c9b5194842f777db..e0d14bd9ea21c966773bde4b6ab75a0ca113aeac 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -483,6 +483,9 @@ numpy==1.19.2
 # homeassistant.components.google
 oauth2client==4.0.0
 
+# homeassistant.components.omnilogic
+omnilogic==0.4.0
+
 # homeassistant.components.onvif
 onvif-zeep-async==0.5.0
 
diff --git a/tests/components/omnilogic/__init__.py b/tests/components/omnilogic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b8008abaa5dcedafe77cb9fa69fc6038cbf059
--- /dev/null
+++ b/tests/components/omnilogic/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Omnilogic integration."""
diff --git a/tests/components/omnilogic/test_config_flow.py b/tests/components/omnilogic/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef29ff9f67439c031744533df738502f37baf15b
--- /dev/null
+++ b/tests/components/omnilogic/test_config_flow.py
@@ -0,0 +1,147 @@
+"""Test the Omnilogic config flow."""
+from omnilogic import LoginException, OmniLogicException
+
+from homeassistant import config_entries, data_entry_flow, setup
+from homeassistant.components.omnilogic.const import DOMAIN
+
+from tests.async_mock import patch
+from tests.common import MockConfigEntry
+
+DATA = {"username": "test-username", "password": "test-password"}
+
+
+async def test_form(hass):
+    """Test we get the form."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result["type"] == "form"
+    assert result["errors"] == {}
+
+    with patch(
+        "homeassistant.components.omnilogic.config_flow.OmniLogic.connect",
+        return_value=True,
+    ), patch(
+        "homeassistant.components.omnilogic.async_setup", return_value=True
+    ) as mock_setup, patch(
+        "homeassistant.components.omnilogic.async_setup_entry",
+        return_value=True,
+    ) as mock_setup_entry:
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            DATA,
+        )
+
+    assert result2["type"] == "create_entry"
+    assert result2["title"] == "Omnilogic"
+    assert result2["data"] == DATA
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_already_configured(hass):
+    """Test config flow when Omnilogic component is already setup."""
+    MockConfigEntry(domain="omnilogic", data=DATA).add_to_hass(hass)
+
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    assert result["type"] == "abort"
+    assert result["reason"] == "single_instance_allowed"
+
+
+async def test_with_invalid_credentials(hass):
+    """Test with invalid credentials."""
+
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    with patch(
+        "homeassistant.components.omnilogic.OmniLogic.connect",
+        side_effect=LoginException,
+    ):
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            DATA,
+        )
+
+    assert result["type"] == "form"
+    assert result["step_id"] == "user"
+    assert result["errors"] == {"base": "invalid_auth"}
+
+
+async def test_form_cannot_connect(hass):
+    """Test if invalid response or no connection returned from Hayward."""
+
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    with patch(
+        "homeassistant.components.omnilogic.OmniLogic.connect",
+        side_effect=OmniLogicException,
+    ):
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            DATA,
+        )
+
+    assert result["type"] == "form"
+    assert result["step_id"] == "user"
+    assert result["errors"] == {"base": "cannot_connect"}
+
+
+async def test_with_unknown_error(hass):
+    """Test with unknown error response from Hayward."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    with patch(
+        "homeassistant.components.omnilogic.OmniLogic.connect",
+        side_effect=Exception,
+    ):
+        result = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            DATA,
+        )
+
+    assert result["type"] == "form"
+    assert result["step_id"] == "user"
+    assert result["errors"] == {"base": "unknown"}
+
+
+async def test_option_flow(hass):
+    """Test option flow."""
+    entry = MockConfigEntry(domain=DOMAIN, data=DATA)
+    entry.add_to_hass(hass)
+
+    assert not entry.options
+
+    with patch(
+        "homeassistant.components.omnilogic.async_setup_entry", return_value=True
+    ):
+        result = await hass.config_entries.options.async_init(
+            entry.entry_id,
+            data=None,
+        )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+    assert result["step_id"] == "init"
+
+    result = await hass.config_entries.options.async_configure(
+        result["flow_id"],
+        user_input={"polling_interval": 9},
+    )
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == ""
+    assert result["data"]["polling_interval"] == 9