core/homeassistant/components/isy994/__init__.py

275 lines
8.3 KiB
Python
Raw Normal View History

"""Support the ISY-994 controllers."""
import asyncio
from functools import partial
from typing import Optional
2015-04-04 08:33:03 +00:00
from urllib.parse import urlparse
from pyisy import ISY
import voluptuous as vol
2015-04-04 08:33:03 +00:00
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.typing import ConfigType
from .const import (
_LOGGER,
CONF_IGNORE_STRING,
CONF_RESTORE_LIGHT_STATE,
CONF_SENSOR_STRING,
CONF_TLS_VER,
CONF_VAR_SENSOR_STRING,
DEFAULT_IGNORE_STRING,
DEFAULT_RESTORE_LIGHT_STATE,
DEFAULT_SENSOR_STRING,
DEFAULT_VAR_SENSOR_STRING,
DOMAIN,
ISY994_ISY,
ISY994_NODES,
ISY994_PROGRAMS,
ISY994_VARIABLES,
MANUFACTURER,
PLATFORMS,
SUPPORTED_PROGRAM_PLATFORMS,
UNDO_UPDATE_LISTENER,
)
from .helpers import _categorize_nodes, _categorize_programs, _categorize_variables
from .services import async_setup_services, async_unload_services
2019-07-31 19:25:30 +00:00
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST): cv.url,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_TLS_VER): vol.Coerce(float),
vol.Optional(
CONF_IGNORE_STRING, default=DEFAULT_IGNORE_STRING
): cv.string,
vol.Optional(
CONF_SENSOR_STRING, default=DEFAULT_SENSOR_STRING
): cv.string,
vol.Optional(
CONF_VAR_SENSOR_STRING, default=DEFAULT_VAR_SENSOR_STRING
): cv.string,
vol.Required(
CONF_RESTORE_LIGHT_STATE, default=DEFAULT_RESTORE_LIGHT_STATE
): bool,
2019-07-31 19:25:30 +00:00
}
)
},
extra=vol.ALLOW_EXTRA,
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the isy994 integration from YAML."""
isy_config: Optional[ConfigType] = config.get(DOMAIN)
hass.data.setdefault(DOMAIN, {})
if not isy_config:
return True
# Only import if we haven't before.
config_entry = _async_find_matching_config_entry(hass)
if not config_entry:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=dict(isy_config),
)
)
return True
# Update the entry based on the YAML configuration, in case it changed.
hass.config_entries.async_update_entry(config_entry, data=dict(isy_config))
return True
@callback
def _async_find_matching_config_entry(hass):
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.source == config_entries.SOURCE_IMPORT:
return entry
async def async_setup_entry(
hass: HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Set up the ISY 994 integration."""
# As there currently is no way to import options from yaml
# when setting up a config entry, we fallback to adding
# the options to the config entry and pull them out here if
# they are missing from the options
_async_import_options_from_data_if_missing(hass, entry)
hass.data[DOMAIN][entry.entry_id] = {}
hass_isy_data = hass.data[DOMAIN][entry.entry_id]
hass_isy_data[ISY994_NODES] = {}
for platform in PLATFORMS:
hass_isy_data[ISY994_NODES][platform] = []
Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware (#11243) * Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware # * No more globals - store on hass.data # * Parent ISY994 component handles categorizing nodes in to Hass components, rather than each individual domain filtering all nodes themselves # * Remove hidden string, replace with ignore string. Hidden should be done via the customize block; ignore fully prevents the node from getting a Hass entity # * Removed a few unused methods in the ISYDevice class # * Cleaned up the hostname parsing # * Removed broken logic in the fan Program component. It was setting properties that have no setters # * Added the missing SUPPORTED_FEATURES to the fan component to indicate that it can set speed # * Added better error handling and a log warning when an ISY994 program entity fails to initialize # * Cleaned up a few instances of unecessarily complicated logic paths, and other cases of unnecessary logic that is already handled by base classes * Use `super()` instead of explicit base class calls * Move `hass` argument to first position * Use str.format instead of string addition * Move program structure building and validation to component Removes the need for a bunch of duplicate exception handling in each individual platform * Fix climate nodes, fix climate names, add config to disable climate Sensor platform was crashing when the ISY reported climate nodes. Logic has been fixed. Also added a config option to prevent climate sensors from getting imported from the ISY. Also replace the underscore from climate node names with spaces so they default to friendly names. * Space missing in error message * Fix string comparison to use `==` * Explicitly check for attributes rather than catch AttributeError Also removes two stray debug lines * Remove null checks on hass.data, as they are always null at this point
2017-12-26 08:26:37 +00:00
hass_isy_data[ISY994_PROGRAMS] = {}
for platform in SUPPORTED_PROGRAM_PLATFORMS:
hass_isy_data[ISY994_PROGRAMS][platform] = []
Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware (#11243) * Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware # * No more globals - store on hass.data # * Parent ISY994 component handles categorizing nodes in to Hass components, rather than each individual domain filtering all nodes themselves # * Remove hidden string, replace with ignore string. Hidden should be done via the customize block; ignore fully prevents the node from getting a Hass entity # * Removed a few unused methods in the ISYDevice class # * Cleaned up the hostname parsing # * Removed broken logic in the fan Program component. It was setting properties that have no setters # * Added the missing SUPPORTED_FEATURES to the fan component to indicate that it can set speed # * Added better error handling and a log warning when an ISY994 program entity fails to initialize # * Cleaned up a few instances of unecessarily complicated logic paths, and other cases of unnecessary logic that is already handled by base classes * Use `super()` instead of explicit base class calls * Move `hass` argument to first position * Use str.format instead of string addition * Move program structure building and validation to component Removes the need for a bunch of duplicate exception handling in each individual platform * Fix climate nodes, fix climate names, add config to disable climate Sensor platform was crashing when the ISY reported climate nodes. Logic has been fixed. Also added a config option to prevent climate sensors from getting imported from the ISY. Also replace the underscore from climate node names with spaces so they default to friendly names. * Space missing in error message * Fix string comparison to use `==` * Explicitly check for attributes rather than catch AttributeError Also removes two stray debug lines * Remove null checks on hass.data, as they are always null at this point
2017-12-26 08:26:37 +00:00
hass_isy_data[ISY994_VARIABLES] = []
isy_config = entry.data
isy_options = entry.options
# Required
user = isy_config[CONF_USERNAME]
password = isy_config[CONF_PASSWORD]
host = urlparse(isy_config[CONF_HOST])
# Optional
tls_version = isy_config.get(CONF_TLS_VER)
ignore_identifier = isy_options.get(CONF_IGNORE_STRING, DEFAULT_IGNORE_STRING)
sensor_identifier = isy_options.get(CONF_SENSOR_STRING, DEFAULT_SENSOR_STRING)
variable_identifier = isy_options.get(
CONF_VAR_SENSOR_STRING, DEFAULT_VAR_SENSOR_STRING
)
2019-07-31 19:25:30 +00:00
if host.scheme == "http":
https = False
Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware (#11243) * Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware # * No more globals - store on hass.data # * Parent ISY994 component handles categorizing nodes in to Hass components, rather than each individual domain filtering all nodes themselves # * Remove hidden string, replace with ignore string. Hidden should be done via the customize block; ignore fully prevents the node from getting a Hass entity # * Removed a few unused methods in the ISYDevice class # * Cleaned up the hostname parsing # * Removed broken logic in the fan Program component. It was setting properties that have no setters # * Added the missing SUPPORTED_FEATURES to the fan component to indicate that it can set speed # * Added better error handling and a log warning when an ISY994 program entity fails to initialize # * Cleaned up a few instances of unecessarily complicated logic paths, and other cases of unnecessary logic that is already handled by base classes * Use `super()` instead of explicit base class calls * Move `hass` argument to first position * Use str.format instead of string addition * Move program structure building and validation to component Removes the need for a bunch of duplicate exception handling in each individual platform * Fix climate nodes, fix climate names, add config to disable climate Sensor platform was crashing when the ISY reported climate nodes. Logic has been fixed. Also added a config option to prevent climate sensors from getting imported from the ISY. Also replace the underscore from climate node names with spaces so they default to friendly names. * Space missing in error message * Fix string comparison to use `==` * Explicitly check for attributes rather than catch AttributeError Also removes two stray debug lines * Remove null checks on hass.data, as they are always null at this point
2017-12-26 08:26:37 +00:00
port = host.port or 80
2019-07-31 19:25:30 +00:00
elif host.scheme == "https":
https = True
Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware (#11243) * Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware # * No more globals - store on hass.data # * Parent ISY994 component handles categorizing nodes in to Hass components, rather than each individual domain filtering all nodes themselves # * Remove hidden string, replace with ignore string. Hidden should be done via the customize block; ignore fully prevents the node from getting a Hass entity # * Removed a few unused methods in the ISYDevice class # * Cleaned up the hostname parsing # * Removed broken logic in the fan Program component. It was setting properties that have no setters # * Added the missing SUPPORTED_FEATURES to the fan component to indicate that it can set speed # * Added better error handling and a log warning when an ISY994 program entity fails to initialize # * Cleaned up a few instances of unecessarily complicated logic paths, and other cases of unnecessary logic that is already handled by base classes * Use `super()` instead of explicit base class calls * Move `hass` argument to first position * Use str.format instead of string addition * Move program structure building and validation to component Removes the need for a bunch of duplicate exception handling in each individual platform * Fix climate nodes, fix climate names, add config to disable climate Sensor platform was crashing when the ISY reported climate nodes. Logic has been fixed. Also added a config option to prevent climate sensors from getting imported from the ISY. Also replace the underscore from climate node names with spaces so they default to friendly names. * Space missing in error message * Fix string comparison to use `==` * Explicitly check for attributes rather than catch AttributeError Also removes two stray debug lines * Remove null checks on hass.data, as they are always null at this point
2017-12-26 08:26:37 +00:00
port = host.port or 443
2015-04-04 08:33:03 +00:00
else:
_LOGGER.error("isy994 host value in configuration is invalid")
return False
2016-03-07 17:49:31 +00:00
# Connect to ISY controller.
isy = await hass.async_add_executor_job(
partial(
ISY,
host.hostname,
port,
username=user,
password=password,
use_https=https,
tls_ver=tls_version,
webroot=host.path,
)
2019-07-31 19:25:30 +00:00
)
Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware (#11243) * Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware # * No more globals - store on hass.data # * Parent ISY994 component handles categorizing nodes in to Hass components, rather than each individual domain filtering all nodes themselves # * Remove hidden string, replace with ignore string. Hidden should be done via the customize block; ignore fully prevents the node from getting a Hass entity # * Removed a few unused methods in the ISYDevice class # * Cleaned up the hostname parsing # * Removed broken logic in the fan Program component. It was setting properties that have no setters # * Added the missing SUPPORTED_FEATURES to the fan component to indicate that it can set speed # * Added better error handling and a log warning when an ISY994 program entity fails to initialize # * Cleaned up a few instances of unecessarily complicated logic paths, and other cases of unnecessary logic that is already handled by base classes * Use `super()` instead of explicit base class calls * Move `hass` argument to first position * Use str.format instead of string addition * Move program structure building and validation to component Removes the need for a bunch of duplicate exception handling in each individual platform * Fix climate nodes, fix climate names, add config to disable climate Sensor platform was crashing when the ISY reported climate nodes. Logic has been fixed. Also added a config option to prevent climate sensors from getting imported from the ISY. Also replace the underscore from climate node names with spaces so they default to friendly names. * Space missing in error message * Fix string comparison to use `==` * Explicitly check for attributes rather than catch AttributeError Also removes two stray debug lines * Remove null checks on hass.data, as they are always null at this point
2017-12-26 08:26:37 +00:00
if not isy.connected:
2015-04-04 08:33:03 +00:00
return False
# Trigger a status update for all nodes, not done automatically in PyISY v2.x
await hass.async_add_executor_job(isy.nodes.update)
_categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier)
_categorize_programs(hass_isy_data, isy.programs)
_categorize_variables(hass_isy_data, isy.variables, variable_identifier)
# Dump ISY Clock Information. Future: Add ISY as sensor to Hass with attrs
_LOGGER.info(repr(isy.clock))
hass_isy_data[ISY994_ISY] = isy
await _async_get_or_create_isy_device_in_registry(hass, entry, isy)
# Load platforms for the devices in the ISY controller that we support.
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
def _start_auto_update() -> None:
"""Start isy auto update."""
_LOGGER.debug("ISY Starting Event Stream and automatic updates")
isy.auto_update = True
await hass.async_add_executor_job(_start_auto_update)
undo_listener = entry.add_update_listener(_async_update_listener)
hass_isy_data[UNDO_UPDATE_LISTENER] = undo_listener
2015-04-04 08:33:03 +00:00
# Register Integration-wide Services:
async_setup_services(hass)
2015-04-04 08:33:03 +00:00
return True
async def _async_update_listener(
hass: HomeAssistant, entry: config_entries.ConfigEntry
):
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
@callback
def _async_import_options_from_data_if_missing(
hass: HomeAssistant, entry: config_entries.ConfigEntry
):
options = dict(entry.options)
modified = False
for importable_option in [
CONF_IGNORE_STRING,
CONF_SENSOR_STRING,
CONF_RESTORE_LIGHT_STATE,
]:
if importable_option not in entry.options and importable_option in entry.data:
options[importable_option] = entry.data[importable_option]
modified = True
if modified:
hass.config_entries.async_update_entry(entry, options=options)
async def _async_get_or_create_isy_device_in_registry(
hass: HomeAssistant, entry: config_entries.ConfigEntry, isy
) -> None:
device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, isy.configuration["uuid"])},
identifiers={(DOMAIN, isy.configuration["uuid"])},
manufacturer=MANUFACTURER,
name=isy.configuration["name"],
model=isy.configuration["model"],
sw_version=isy.configuration["firmware"],
)
async def async_unload_entry(
hass: HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in PLATFORMS
]
)
)
hass_isy_data = hass.data[DOMAIN][entry.entry_id]
isy = hass_isy_data[ISY994_ISY]
def _stop_auto_update() -> None:
"""Start isy auto update."""
_LOGGER.debug("ISY Stopping Event Stream and automatic updates")
isy.auto_update = False
await hass.async_add_executor_job(_stop_auto_update)
hass_isy_data[UNDO_UPDATE_LISTENER]()
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
async_unload_services(hass)
return unload_ok