mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add Omnilogic integration (#40474)
* Scaffold * Added the en translation * Modified the name * Basic functionality for config flow. * Pulled in enough to validate config flow works. * Update manifest.json * initial data polling (water and air temp sensors) * Adding sensors, debugging update function * polling updates working * support for new data format from library * Updated entity_id, friendly name, conversion for ppm, attributes for hayward display units, MSPSystemID and component systemID * Fixed errors for PR * clean up * Add login exc, check if configured, test login. * Remove debug print. * Black formatting, ran isort, update requirements. * Updated w isort. fix flake8 failures. * Fix flake8 errors * Fixed self.attrs to remove invalid self._ values - small change * Missed on small change - fixing attributes * Updated naming, updated unit of measure, updated icon, bumped omnilog… * Updated to fix flake8 issues in __init__.py and config_flow.py * Updated test_config_flow.py to pass, updated config_flow.py to correct errors in test * Remove comments in preparation for PR * update .covezragerc * Formatting fix * Rewrote sensors to dynamically add all BOWs, pumps, clorinators. Still to do - add CSAD sensors. * Rewrote sensors to dynamically add all BOWs, pumps, clorinators. Still to do - add CSAD sensors. * Added CSAD sensors for pools that have them. * Added CSAD sensors for pools that have them. * Fixed CSAD to not create if blank or don't exist, removed broad except usage to pass linting. * Updated entity naming convention. Fixed linting issues. * Added device association to the back yard / omnilogic system * Removed .0 from ppm values when returning imperial values for salt sensor * Updated to return state = None for water temp when pump is off, handled Chlorinator operatingMode = 2, and added PlatformNotReady check * Corrected exception from Omnilogic library * Bumped omnilogic to 0.3.7. Added alarm sensor/data to sensors. Handle pump off condition for ph and orp sensors. * Bumped omnilogic to 0.3.7. Added alarm sensor/data to sensors. Handle pump off condition for ph and orp sensors. * Bumped omnilogic to 0.3.7. Added alarm sensor/data to sensors. Handle pump off condition for ph and orp sensors. * Removed nested_lookup dependency, bumped omnilogic.py to 0.3.8. * Fixed lint error * Added logging for sensor creation. * Fixed linting errors with logging. * Fixed explicit chaining of raised error. Fixed issue with alarm sensor. * Fixed manifest.json based on feedback. * Fixed self.attrs, should_poll, CoordinatorEntity, SCAN_INTERVAL from comments in PR. * Addressed unique_id, moved data update coordinator, addressed minor other issues from testing * Created main OmniLogic entity for common items, reworked DataUpdateCoordinator to it's own class. * Addressed config_schema not used in __init__.py * Fixed linting issues. * Addressed several comments, still todo - separate sensor classes. * Split the Omnilogic Sensors into separate logical classes for simpler logic. * Fixed snake case lint error for AddAlarms (to add_alarms) * Addressed config_flow issues from comments. * Changed addressed ConfigNotReady issue from comments. * Updated strings.json and generated corrected en.json with translations. * Updated en.json to standard generated file. * Added config_flow tests and updated issue with config_flow on cannot_connect * Added test case for incomplete information entered. * Compressed logic in the sensor classes to reduce duplication. * Updated strings.json for polling_interval, added generic exception handling on config flow. * Removed omnilogic from the .coveragerc omit file. * Updated test_config_flow to follow recommended pattern. * Excluded sensor.py from test coverage tests. * Corected minor issues in test_config_flow from comments * Fixed linting issues on last commits * Fixed linting issues. * Corrected issue when temp state is not available from Omnilogic * Added omnililogic_common.py from .coveragerc to bypass test coverage check. * Return false on Login Exception, handle OmniLogicException in config_flow and in tests. * Handle all exceptions and in config_flow and tests, clarified test naming. * Broke out test cases per comments. * Regenerated en.json file. * Addressed changes from comments in PR. * Added session and bumped API to 0.4.0, addressed other comments from PR. * Addressed entitydata (missed earlier). * Fixed pylint issue * Added test case for options flow in test_config_flow.py * Removed super() and used self when calling methods in current class. * Addressed comments in PR. * Addressed comments in PR. * Updated translations file. * Rewrote data coordinator to output dict for easy searching. * Updated chlorinator unit when chlorinator is on/off only * Scaffold * Added the en translation * Modified the name * Basic functionality for config flow. * Pulled in enough to validate config flow works. * Update manifest.json * initial data polling (water and air temp sensors) * Adding sensors, debugging update function * polling updates working * support for new data format from library * Updated entity_id, friendly name, conversion for ppm, attributes for hayward display units, MSPSystemID and component systemID * Fixed errors for PR * clean up * Add login exc, check if configured, test login. * Remove debug print. * Black formatting, ran isort, update requirements. * Updated w isort. fix flake8 failures. * Fix flake8 errors * Fixed self.attrs to remove invalid self._ values - small change * Missed on small change - fixing attributes * Updated naming, updated unit of measure, updated icon, bumped omnilog… * Updated to fix flake8 issues in __init__.py and config_flow.py * Updated test_config_flow.py to pass, updated config_flow.py to correct errors in test * Remove comments in preparation for PR * update .covezragerc * Formatting fix * Rewrote sensors to dynamically add all BOWs, pumps, clorinators. Still to do - add CSAD sensors. * Rewrote sensors to dynamically add all BOWs, pumps, clorinators. Still to do - add CSAD sensors. * Added CSAD sensors for pools that have them. * Added CSAD sensors for pools that have them. * Fixed CSAD to not create if blank or don't exist, removed broad except usage to pass linting. * Updated entity naming convention. Fixed linting issues. * Added device association to the back yard / omnilogic system * Removed .0 from ppm values when returning imperial values for salt sensor * Updated to return state = None for water temp when pump is off, handled Chlorinator operatingMode = 2, and added PlatformNotReady check * Corrected exception from Omnilogic library * Bumped omnilogic to 0.3.7. Added alarm sensor/data to sensors. Handle pump off condition for ph and orp sensors. * Bumped omnilogic to 0.3.7. Added alarm sensor/data to sensors. Handle pump off condition for ph and orp sensors. * Bumped omnilogic to 0.3.7. Added alarm sensor/data to sensors. Handle pump off condition for ph and orp sensors. * Removed nested_lookup dependency, bumped omnilogic.py to 0.3.8. * Fixed lint error * Added logging for sensor creation. * Fixed linting errors with logging. * Fixed explicit chaining of raised error. Fixed issue with alarm sensor. * Fixed manifest.json based on feedback. * Fixed self.attrs, should_poll, CoordinatorEntity, SCAN_INTERVAL from comments in PR. * Addressed unique_id, moved data update coordinator, addressed minor other issues from testing * Created main OmniLogic entity for common items, reworked DataUpdateCoordinator to it's own class. * Addressed config_schema not used in __init__.py * Fixed linting issues. * Addressed several comments, still todo - separate sensor classes. * Split the Omnilogic Sensors into separate logical classes for simpler logic. * Fixed snake case lint error for AddAlarms (to add_alarms) * Addressed config_flow issues from comments. * Changed addressed ConfigNotReady issue from comments. * Updated strings.json and generated corrected en.json with translations. * Updated en.json to standard generated file. * Added config_flow tests and updated issue with config_flow on cannot_connect * Added test case for incomplete information entered. * Compressed logic in the sensor classes to reduce duplication. * Updated strings.json for polling_interval, added generic exception handling on config flow. * Removed omnilogic from the .coveragerc omit file. * Updated test_config_flow to follow recommended pattern. * Excluded sensor.py from test coverage tests. * Corected minor issues in test_config_flow from comments * Fixed linting issues on last commits * Fixed linting issues. * Corrected issue when temp state is not available from Omnilogic * Added omnililogic_common.py from .coveragerc to bypass test coverage check. * Return false on Login Exception, handle OmniLogicException in config_flow and in tests. * Handle all exceptions and in config_flow and tests, clarified test naming. * Broke out test cases per comments. * Regenerated en.json file. * Addressed changes from comments in PR. * Added session and bumped API to 0.4.0, addressed other comments from PR. * Addressed entitydata (missed earlier). * Fixed pylint issue * Added test case for options flow in test_config_flow.py * Removed super() and used self when calling methods in current class. * Addressed comments in PR. * Addressed comments in PR. * Updated translations file. * Rewrote data coordinator to output dict for easy searching. * Updated chlorinator unit when chlorinator is on/off only * Fixed ORP method not being @property, fixed unique_id potential issue. Does not address comments from PR. * Rewrote coordinator for updated dict structure, rewrote sensors to parse new data structure. * Added alarms as attributes on all entities which support alarm reporting. * Updated SENSOR_TYPES to sensor_types to adhere to snake case in pylint. * Addressed PR comments. * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Removed binary sensor conditions (alarms, on/off sensor types) and added ability for multiple guard conditions * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Updated per comments in PR for Pump Type and removal of force_update(). * Update homeassistant/components/omnilogic/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/omnilogic/common.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Correctly asserting conditions for the login exception case. * Update .coveragerc Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Mike Hershberger <mike.hershberger@gmail.com> Co-authored-by: Chad <54695185+chadlyy@users.noreply.github.com> Co-authored-by: Tim Empringham <tim.empringham@live.ca> Co-authored-by: djtimca <60706061+djtimca@users.noreply.github.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
318096be79
commit
0c12af347e
@ -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
|
||||
|
@ -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
|
||||
|
90
homeassistant/components/omnilogic/__init__.py
Normal file
90
homeassistant/components/omnilogic/__init__.py
Normal file
@ -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
|
157
homeassistant/components/omnilogic/common.py
Normal file
157
homeassistant/components/omnilogic/common.py
Normal file
@ -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",
|
||||
}
|
95
homeassistant/components/omnilogic/config_flow.py
Normal file
95
homeassistant/components/omnilogic/config_flow.py
Normal file
@ -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,
|
||||
}
|
||||
),
|
||||
)
|
29
homeassistant/components/omnilogic/const.py
Normal file
29
homeassistant/components/omnilogic/const.py
Normal file
@ -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",
|
||||
}
|
8
homeassistant/components/omnilogic/manifest.json
Normal file
8
homeassistant/components/omnilogic/manifest.json
Normal file
@ -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"]
|
||||
}
|
356
homeassistant/components/omnilogic/sensor.py
Normal file
356
homeassistant/components/omnilogic/sensor.py
Normal file
@ -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": ""},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
30
homeassistant/components/omnilogic/strings.json
Normal file
30
homeassistant/components/omnilogic/strings.json
Normal file
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
homeassistant/components/omnilogic/translations/en.json
Normal file
30
homeassistant/components/omnilogic/translations/en.json
Normal file
@ -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"
|
||||
}
|
@ -127,6 +127,7 @@ FLOWS = [
|
||||
"nut",
|
||||
"nws",
|
||||
"nzbget",
|
||||
"omnilogic",
|
||||
"onvif",
|
||||
"opentherm_gw",
|
||||
"openuv",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
1
tests/components/omnilogic/__init__.py
Normal file
1
tests/components/omnilogic/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the Omnilogic integration."""
|
147
tests/components/omnilogic/test_config_flow.py
Normal file
147
tests/components/omnilogic/test_config_flow.py
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user