Skip to content

Commit

Permalink
Add support for multiple IHC controllers (#18058)
Browse files Browse the repository at this point in the history
* Added support for secondary IHC controller

Most IHC systems only have one controller but the system can be setup with a linked secondary controller.
I have updated the code to have it support both primary and secondary controller. Existing configuration is not impacted and secondary controller can be setup the same way, with similar settings nested under 'secondary' in the configuration

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update __init__.py

* Update const.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update const.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

indentation was incorrect for "load_platform" in "get_manual_configuration". Load_platform was not called with the correct component name
  • Loading branch information
mopolus authored and MartinHjelmare committed Nov 22, 2018
1 parent 22ab83a commit 01ee03a
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 180 deletions.
62 changes: 21 additions & 41 deletions homeassistant/components/binary_sensor/ihc.py
Expand Up @@ -3,59 +3,39 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ihc/
"""
import voluptuous as vol

from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
BinarySensorDevice)
from homeassistant.components.ihc import (
validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO)
from homeassistant.components.ihc.const import CONF_INVERTING
IHC_DATA, IHC_CONTROLLER, IHC_INFO)
from homeassistant.components.ihc.const import (
CONF_INVERTING)
from homeassistant.components.ihc.ihcdevice import IHCDevice
from homeassistant.const import (
CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS)
import homeassistant.helpers.config_validation as cv
CONF_TYPE)

DEPENDENCIES = ['ihc']

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BINARY_SENSORS, default=[]):
vol.All(cv.ensure_list, [
vol.All({
vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
}, validate_name)
])
})


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the IHC binary sensor platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
if discovery_info is None:
return
devices = []
if discovery_info:
for name, device in discovery_info.items():
ihc_id = device['ihc_id']
product_cfg = device['product_cfg']
product = device['product']
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
else:
binary_sensors = config[CONF_BINARY_SENSORS]
for sensor_cfg in binary_sensors:
ihc_id = sensor_cfg[CONF_ID]
name = sensor_cfg[CONF_NAME]
sensor_type = sensor_cfg.get(CONF_TYPE)
inverting = sensor_cfg[CONF_INVERTING]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
sensor_type, inverting)
devices.append(sensor)
for name, device in discovery_info.items():
ihc_id = device['ihc_id']
product_cfg = device['product_cfg']
product = device['product']
# Find controller that corresponds with device id
ctrl_id = device['ctrl_id']
ihc_key = IHC_DATA.format(ctrl_id)
info = hass.data[ihc_key][IHC_INFO]
ihc_controller = hass.data[ihc_key][IHC_CONTROLLER]

sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
add_entities(devices)


Expand Down
170 changes: 137 additions & 33 deletions homeassistant/components/ihc/__init__.py
Expand Up @@ -9,38 +9,106 @@
import xml.etree.ElementTree

import voluptuous as vol

from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.components.ihc.const import (
ATTR_IHC_ID, ATTR_VALUE, CONF_AUTOSETUP, CONF_BINARY_SENSOR, CONF_DIMMABLE,
CONF_INFO, CONF_INVERTING, CONF_LIGHT, CONF_NODE, CONF_SENSOR, CONF_SWITCH,
CONF_XPATH, SERVICE_SET_RUNTIME_VALUE_BOOL,
CONF_INFO, CONF_INVERTING, CONF_LIGHT, CONF_NODE, CONF_NOTE, CONF_POSITION,
CONF_SENSOR, CONF_SWITCH, CONF_XPATH, SERVICE_SET_RUNTIME_VALUE_BOOL,
SERVICE_SET_RUNTIME_VALUE_FLOAT, SERVICE_SET_RUNTIME_VALUE_INT)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
CONF_ID, CONF_NAME, CONF_PASSWORD, CONF_TYPE, CONF_UNIT_OF_MEASUREMENT,
CONF_URL, CONF_USERNAME, TEMP_CELSIUS)
CONF_BINARY_SENSORS, CONF_ID, CONF_LIGHTS, CONF_NAME, CONF_PASSWORD,
CONF_SENSORS, CONF_SWITCHES, CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_URL,
CONF_USERNAME, TEMP_CELSIUS)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType

REQUIREMENTS = ['ihcsdk==2.2.0']

DOMAIN = 'ihc'
IHC_DATA = 'ihc'
IHC_DATA = 'ihc{}'
IHC_CONTROLLER = 'controller'
IHC_INFO = 'info'
AUTO_SETUP_YAML = 'ihc_auto_setup.yaml'


def validate_name(config):
"""Validate device name."""
if CONF_NAME in config:
return config
ihcid = config[CONF_ID]
name = 'ihc_{}'.format(ihcid)
config[CONF_NAME] = name
return config


DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_POSITION): cv.string,
vol.Optional(CONF_NOTE): cv.string
}, extra=vol.ALLOW_EXTRA)


SWITCH_SCHEMA = DEVICE_SCHEMA.extend({
})

BINARY_SENSOR_SCHEMA = DEVICE_SCHEMA.extend({
vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
})

LIGHT_SCHEMA = DEVICE_SCHEMA.extend({
vol.Optional(CONF_DIMMABLE, default=False): cv.boolean,
})

SENSOR_SCHEMA = DEVICE_SCHEMA.extend({
vol.Optional(CONF_UNIT_OF_MEASUREMENT,
default=TEMP_CELSIUS): cv.string,
})

IHC_SCHEMA = vol.Schema({
vol.Required(CONF_URL): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AUTOSETUP, default=True): cv.boolean,
vol.Optional(CONF_INFO, default=True): cv.boolean,
vol.Optional(CONF_BINARY_SENSORS, default=[]):
vol.All(cv.ensure_list, [
vol.All(
BINARY_SENSOR_SCHEMA,
validate_name)
]),
vol.Optional(CONF_LIGHTS, default=[]):
vol.All(cv.ensure_list, [
vol.All(
LIGHT_SCHEMA,
validate_name)
]),
vol.Optional(CONF_SENSORS, default=[]):
vol.All(cv.ensure_list, [
vol.All(
SENSOR_SCHEMA,
validate_name)
]),
vol.Optional(CONF_SWITCHES, default=[]):
vol.All(cv.ensure_list, [
vol.All(
SWITCH_SCHEMA,
validate_name)
]),
}, extra=vol.ALLOW_EXTRA)

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_URL): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AUTOSETUP, default=True): cv.boolean,
vol.Optional(CONF_INFO, default=True): cv.boolean,
}),
DOMAIN: vol.Schema(vol.All(
cv.ensure_list,
[IHC_SCHEMA]
)),
}, extra=vol.ALLOW_EXTRA)


AUTO_SETUP_SCHEMA = vol.Schema({
vol.Optional(CONF_BINARY_SENSOR, default=[]):
vol.All(cv.ensure_list, [
Expand Down Expand Up @@ -98,35 +166,79 @@


def setup(hass, config):
"""Set up the IHC platform."""
conf = config.get(DOMAIN)
for index, controller_conf in enumerate(conf):
if not ihc_setup(hass, config, controller_conf, index):
return False

return True


def ihc_setup(hass, config, conf, controller_id):
"""Set up the IHC component."""
from ihcsdk.ihccontroller import IHCController
conf = config[DOMAIN]

url = conf[CONF_URL]
username = conf[CONF_USERNAME]
password = conf[CONF_PASSWORD]
ihc_controller = IHCController(url, username, password)

ihc_controller = IHCController(url, username, password)
if not ihc_controller.authenticate():
_LOGGER.error("Unable to authenticate on IHC controller")
return False

if (conf[CONF_AUTOSETUP] and
not autosetup_ihc_products(hass, config, ihc_controller)):
not autosetup_ihc_products(hass, config, ihc_controller,
controller_id)):
return False

hass.data[IHC_DATA] = {
# Manual configuration
get_manual_configuration(hass, config, conf, ihc_controller,
controller_id)
# Store controler configuration
ihc_key = IHC_DATA.format(controller_id)
hass.data[ihc_key] = {
IHC_CONTROLLER: ihc_controller,
IHC_INFO: conf[CONF_INFO]}

setup_service_functions(hass, ihc_controller)
return True


def autosetup_ihc_products(hass: HomeAssistantType, config, ihc_controller):
def get_manual_configuration(hass, config, conf, ihc_controller,
controller_id):
"""Get manual configuration for IHC devices."""
for component in IHC_PLATFORMS:
discovery_info = {}
if component in conf:
component_setup = conf.get(component)
for sensor_cfg in component_setup:
name = sensor_cfg[CONF_NAME]
device = {
'ihc_id': sensor_cfg[CONF_ID],
'ctrl_id': controller_id,
'product': {
'name': name,
'note': sensor_cfg.get(CONF_NOTE) or '',
'position': sensor_cfg.get(CONF_POSITION) or ''},
'product_cfg': {
'type': sensor_cfg.get(CONF_TYPE),
'inverting': sensor_cfg.get(CONF_INVERTING),
'dimmable': sensor_cfg.get(CONF_DIMMABLE),
'unit': sensor_cfg.get(CONF_UNIT_OF_MEASUREMENT)
}
}
discovery_info[name] = device
if discovery_info:
discovery.load_platform(hass, component, DOMAIN,
discovery_info, config)


def autosetup_ihc_products(hass: HomeAssistantType, config, ihc_controller,
controller_id):
"""Auto setup of IHC products from the IHC project file."""
project_xml = ihc_controller.get_project()
if not project_xml:
_LOGGER.error("Unable to read project from ICH controller")
_LOGGER.error("Unable to read project from IHC controller")
return False
project = xml.etree.ElementTree.fromstring(project_xml)

Expand All @@ -143,14 +255,15 @@ def autosetup_ihc_products(hass: HomeAssistantType, config, ihc_controller):
groups = project.findall('.//group')
for component in IHC_PLATFORMS:
component_setup = auto_setup_conf[component]
discovery_info = get_discovery_info(component_setup, groups)
discovery_info = get_discovery_info(component_setup, groups,
controller_id)
if discovery_info:
discovery.load_platform(hass, component, DOMAIN, discovery_info,
config)
return True


def get_discovery_info(component_setup, groups):
def get_discovery_info(component_setup, groups, controller_id):
"""Get discovery info for specified IHC component."""
discovery_data = {}
for group in groups:
Expand All @@ -167,6 +280,7 @@ def get_discovery_info(component_setup, groups):
name = '{}_{}'.format(groupname, ihc_id)
device = {
'ihc_id': ihc_id,
'ctrl_id': controller_id,
'product': {
'name': product.attrib['name'],
'note': product.attrib['note'],
Expand Down Expand Up @@ -205,13 +319,3 @@ def set_runtime_value_float(call):
hass.services.register(DOMAIN, SERVICE_SET_RUNTIME_VALUE_FLOAT,
set_runtime_value_float,
schema=SET_RUNTIME_VALUE_FLOAT_SCHEMA)


def validate_name(config):
"""Validate device name."""
if CONF_NAME in config:
return config
ihcid = config[CONF_ID]
name = 'ihc_{}'.format(ihcid)
config[CONF_NAME] = name
return config
3 changes: 3 additions & 0 deletions homeassistant/components/ihc/const.py
Expand Up @@ -10,6 +10,9 @@
CONF_LIGHT = 'light'
CONF_SENSOR = 'sensor'
CONF_SWITCH = 'switch'
CONF_NAME = 'name'
CONF_POSITION = 'position'
CONF_NOTE = 'note'

ATTR_IHC_ID = 'ihc_id'
ATTR_VALUE = 'value'
Expand Down

0 comments on commit 01ee03a

Please sign in to comment.