diff --git a/.coveragerc b/.coveragerc index 8608d30236d66a..e4d5bca95d36a3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -97,6 +97,9 @@ omit = homeassistant/components/homematic/__init__.py homeassistant/components/*/homematic.py + homeassistant/components/ihc/* + homeassistant/components/*/ihc.py + homeassistant/components/insteon_local.py homeassistant/components/*/insteon_local.py diff --git a/homeassistant/components/binary_sensor/ihc.py b/homeassistant/components/binary_sensor/ihc.py new file mode 100644 index 00000000000000..14e45f88cf1fb0 --- /dev/null +++ b/homeassistant/components/binary_sensor/ihc.py @@ -0,0 +1,95 @@ +"""IHC binary sensor platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) +from homeassistant.components.ihc import ( + validate_name, 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 + +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, default=None): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_INVERTING, default=False): cv.boolean, + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, 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] + 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[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[CONF_TYPE] + inverting = sensor_cfg[CONF_INVERTING] + sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, + sensor_type, inverting) + devices.append(sensor) + + add_devices(devices) + + +class IHCBinarySensor(IHCDevice, BinarySensorDevice): + """IHC Binary Sensor. + + The associated IHC resource can be any in or output from a IHC product + or function block, but it must be a boolean ON/OFF resources. + """ + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + sensor_type: str, inverting: bool, product: Element=None): + """Initialize the IHC binary sensor.""" + super().__init__(ihc_controller, name, ihc_id, info, product) + self._state = None + self._sensor_type = sensor_type + self.inverting = inverting + + @property + def device_class(self): + """Return the class of this sensor.""" + return self._sensor_type + + @property + def is_on(self): + """Return true if the binary sensor is on/open.""" + return self._state + + def on_ihc_change(self, ihc_id, value): + """IHC resource has changed.""" + if self.inverting: + self._state = not value + else: + self._state = value + self.schedule_update_ha_state() diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py new file mode 100644 index 00000000000000..f3cd9d790466a5 --- /dev/null +++ b/homeassistant/components/ihc/__init__.py @@ -0,0 +1,213 @@ +"""IHC component. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ihc/ +""" +import logging +import os.path +import xml.etree.ElementTree +import voluptuous as vol + +from homeassistant.components.ihc.const import ( + ATTR_IHC_ID, ATTR_VALUE, CONF_INFO, CONF_AUTOSETUP, + CONF_BINARY_SENSOR, CONF_LIGHT, CONF_SENSOR, CONF_SWITCH, + CONF_XPATH, CONF_NODE, CONF_DIMMABLE, CONF_INVERTING, + SERVICE_SET_RUNTIME_VALUE_BOOL, SERVICE_SET_RUNTIME_VALUE_INT, + SERVICE_SET_RUNTIME_VALUE_FLOAT) +from homeassistant.config import load_yaml_config_file +from homeassistant.const import ( + CONF_URL, CONF_USERNAME, CONF_PASSWORD, CONF_ID, CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, CONF_TYPE, TEMP_CELSIUS) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType + +REQUIREMENTS = ['ihcsdk==2.1.1'] + +DOMAIN = 'ihc' +IHC_DATA = 'ihc' +IHC_CONTROLLER = 'controller' +IHC_INFO = 'info' +AUTO_SETUP_YAML = 'ihc_auto_setup.yaml' + +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 + }), +}, extra=vol.ALLOW_EXTRA) + +AUTO_SETUP_SCHEMA = vol.Schema({ + vol.Optional(CONF_BINARY_SENSOR, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + vol.Optional(CONF_TYPE, default=None): cv.string, + vol.Optional(CONF_INVERTING, default=False): cv.boolean, + }) + ]), + vol.Optional(CONF_LIGHT, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + vol.Optional(CONF_DIMMABLE, default=False): cv.boolean, + }) + ]), + vol.Optional(CONF_SENSOR, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, + default=TEMP_CELSIUS): cv.string, + }) + ]), + vol.Optional(CONF_SWITCH, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + }) + ]), +}) + +SET_RUNTIME_VALUE_BOOL_SCHEMA = vol.Schema({ + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): cv.boolean +}) + +SET_RUNTIME_VALUE_INT_SCHEMA = vol.Schema({ + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): int +}) + +SET_RUNTIME_VALUE_FLOAT_SCHEMA = vol.Schema({ + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): vol.Coerce(float) +}) + +_LOGGER = logging.getLogger(__name__) + +IHC_PLATFORMS = ('binary_sensor', 'light', 'sensor', 'switch') + + +def setup(hass, config): + """Setup 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) + + 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)): + return False + + hass.data[IHC_DATA] = { + 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): + """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 ihc controller.") + return False + project = xml.etree.ElementTree.fromstring(project_xml) + + # if an auto setup file exist in the configuration it will override + yaml_path = hass.config.path(AUTO_SETUP_YAML) + if not os.path.isfile(yaml_path): + yaml_path = os.path.join(os.path.dirname(__file__), AUTO_SETUP_YAML) + yaml = load_yaml_config_file(yaml_path) + try: + auto_setup_conf = AUTO_SETUP_SCHEMA(yaml) + except vol.Invalid as exception: + _LOGGER.error("Invalid IHC auto setup data: %s", exception) + return False + groups = project.findall('.//group') + for component in IHC_PLATFORMS: + component_setup = auto_setup_conf[component] + discovery_info = get_discovery_info(component_setup, groups) + if discovery_info: + discovery.load_platform(hass, component, DOMAIN, discovery_info, + config) + return True + + +def get_discovery_info(component_setup, groups): + """Get discovery info for specified component.""" + discovery_data = {} + for group in groups: + groupname = group.attrib['name'] + for product_cfg in component_setup: + products = group.findall(product_cfg[CONF_XPATH]) + for product in products: + nodes = product.findall(product_cfg[CONF_NODE]) + for node in nodes: + if ('setting' in node.attrib + and node.attrib['setting'] == 'yes'): + continue + ihc_id = int(node.attrib['id'].strip('_'), 0) + name = '{}_{}'.format(groupname, ihc_id) + device = { + 'ihc_id': ihc_id, + 'product': product, + 'product_cfg': product_cfg} + discovery_data[name] = device + return discovery_data + + +def setup_service_functions(hass: HomeAssistantType, ihc_controller): + """Setup the ihc service functions.""" + def set_runtime_value_bool(call): + """Set a IHC runtime bool value service function.""" + ihc_id = call.data[ATTR_IHC_ID] + value = call.data[ATTR_VALUE] + ihc_controller.set_runtime_value_bool(ihc_id, value) + + def set_runtime_value_int(call): + """Set a IHC runtime integer value service function.""" + ihc_id = call.data[ATTR_IHC_ID] + value = call.data[ATTR_VALUE] + ihc_controller.set_runtime_value_int(ihc_id, value) + + def set_runtime_value_float(call): + """Set a IHC runtime float value service function.""" + ihc_id = call.data[ATTR_IHC_ID] + value = call.data[ATTR_VALUE] + ihc_controller.set_runtime_value_float(ihc_id, value) + + hass.services.register(DOMAIN, SERVICE_SET_RUNTIME_VALUE_BOOL, + set_runtime_value_bool, + schema=SET_RUNTIME_VALUE_BOOL_SCHEMA) + hass.services.register(DOMAIN, SERVICE_SET_RUNTIME_VALUE_INT, + set_runtime_value_int, + schema=SET_RUNTIME_VALUE_INT_SCHEMA) + 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 diff --git a/homeassistant/components/ihc/const.py b/homeassistant/components/ihc/const.py new file mode 100644 index 00000000000000..b06746c8e7aef1 --- /dev/null +++ b/homeassistant/components/ihc/const.py @@ -0,0 +1,19 @@ +"""IHC component constants.""" + +CONF_AUTOSETUP = 'auto_setup' +CONF_INFO = 'info' +CONF_XPATH = 'xpath' +CONF_NODE = 'node' +CONF_INVERTING = 'inverting' +CONF_DIMMABLE = 'dimmable' +CONF_BINARY_SENSOR = 'binary_sensor' +CONF_LIGHT = 'light' +CONF_SENSOR = 'sensor' +CONF_SWITCH = 'switch' + +ATTR_IHC_ID = 'ihc_id' +ATTR_VALUE = 'value' + +SERVICE_SET_RUNTIME_VALUE_BOOL = "set_runtime_value_bool" +SERVICE_SET_RUNTIME_VALUE_INT = "set_runtime_value_int" +SERVICE_SET_RUNTIME_VALUE_FLOAT = "set_runtime_value_float" diff --git a/homeassistant/components/ihc/ihc_auto_setup.yaml b/homeassistant/components/ihc/ihc_auto_setup.yaml new file mode 100644 index 00000000000000..81d5bf37977dff --- /dev/null +++ b/homeassistant/components/ihc/ihc_auto_setup.yaml @@ -0,0 +1,98 @@ +# IHC auto setup configuration. +# To customize this, copy this file to the home assistant configuration +# folder and make your changes. + +binary_sensor: + # Magnet contact + - xpath: './/product_dataline[@product_identifier="_0x2109"]' + node: 'dataline_input' + type: 'opening' + inverting: True + # Pir sensors + - xpath: './/product_dataline[@product_identifier="_0x210e"]' + node: 'dataline_input[1]' + type: 'motion' + # Pir sensors twilight sensor + - xpath: './/product_dataline[@product_identifier="_0x0"]' + node: 'dataline_input[1]' + type: 'motion' + # Pir sensors alarm + - xpath: './/product_dataline[@product_identifier="_0x210f"]' + node: 'dataline_input' + type: 'motion' + # Smoke detector + - xpath: './/product_dataline[@product_identifier="_0x210a"]' + node: 'dataline_input' + type: 'smoke' + # leak detector + - xpath: './/product_dataline[@product_identifier="_0x210c"]' + node: 'dataline_input' + type: 'moisture' + # light detector + - xpath: './/product_dataline[@product_identifier="_0x2110"]' + node: 'dataline_input' + type: 'light' + +light: + # Wireless Combi dimmer 4 buttons + - xpath: './/product_airlink[@product_identifier="_0x4406"]' + node: 'airlink_dimming' + dimmable: True + # Wireless Lamp outlet dimmer + - xpath: './/product_airlink[@product_identifier="_0x4304"]' + node: 'airlink_dimming' + dimmable: True + # Wireless universal dimmer + - xpath: './/product_airlink[@product_identifier="_0x4306"]' + node: 'airlink_dimming' + dimmable: True + # Wireless Lamp outlet relay + - xpath: './/product_airlink[@product_identifier="_0x4202"]' + node: 'airlink_relay' + # Wireless Combi relay 4 buttons + - xpath: './/product_airlink[@product_identifier="_0x4404"]' + node: 'airlink_relay' + # Dataline Lamp outlet + - xpath: './/product_dataline[@product_identifier="_0x2202"]' + node: 'dataline_output' + # Mobile Wireless dimmer + - xpath: './/product_airlink[@product_identifier="_0x4303"]' + node: 'airlink_dimming' + dimmable: True + +sensor: + # Temperature sensor + - xpath: './/product_dataline[@product_identifier="_0x2124"]' + node: 'resource_temperature' + unit_of_measurement: '°C' + # Humidity/temperature + - xpath: './/product_dataline[@product_identifier="_0x2135"]' + node: 'resource_humidity_level' + unit_of_measurement: '%' + # Humidity/temperature + - xpath: './/product_dataline[@product_identifier="_0x2135"]' + node: 'resource_temperature' + unit_of_measurement: '°C' + # Lux/temperature + - xpath: './/product_dataline[@product_identifier="_0x2136"]' + node: 'resource_light' + unit_of_measurement: 'Lux' + # Lux/temperature + - xpath: './/product_dataline[@product_identifier="_0x2136"]' + node: 'resource_temperature' + unit_of_measurement: '°C' + +switch: + # Wireless Plug outlet + - xpath: './/product_airlink[@product_identifier="_0x4201"]' + node: 'airlink_relay' + # Dataline universal relay + - xpath: './/product_airlink[@product_identifier="_0x4203"]' + node: 'airlink_relay' + # Dataline plug outlet + - xpath: './/product_dataline[@product_identifier="_0x2201"]' + node: 'dataline_output' + # Wireless mobile relay + - xpath: './/product_airlink[@product_identifier="_0x4204"]' + node: 'airlink_relay' + diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py new file mode 100644 index 00000000000000..48827851f92a00 --- /dev/null +++ b/homeassistant/components/ihc/ihcdevice.py @@ -0,0 +1,65 @@ +"""Implements a base class for all IHC devices.""" +import asyncio +from xml.etree.ElementTree import Element + +from homeassistant.helpers.entity import Entity + + +class IHCDevice(Entity): + """Base class for all ihc devices. + + All IHC devices have an associated IHC resource. IHCDevice handled the + registration of the IHC controller callback when the IHC resource changes. + Derived classes must implement the on_ihc_change method + """ + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + product: Element=None): + """Initialize IHC attributes.""" + self.ihc_controller = ihc_controller + self._name = name + self.ihc_id = ihc_id + self.info = info + if product: + self.ihc_name = product.attrib['name'] + self.ihc_note = product.attrib['note'] + self.ihc_position = product.attrib['position'] + else: + self.ihc_name = '' + self.ihc_note = '' + self.ihc_position = '' + + @asyncio.coroutine + def async_added_to_hass(self): + """Add callback for ihc changes.""" + self.ihc_controller.add_notify_event( + self.ihc_id, self.on_ihc_change, True) + + @property + def should_poll(self) -> bool: + """No polling needed for ihc devices.""" + return False + + @property + def name(self): + """Return the device name.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes.""" + if not self.info: + return {} + return { + 'ihc_id': self.ihc_id, + 'ihc_name': self.ihc_name, + 'ihc_note': self.ihc_note, + 'ihc_position': self.ihc_position + } + + def on_ihc_change(self, ihc_id, value): + """Callback when ihc resource changes. + + Derived classes must overwrite this to do device specific stuff. + """ + raise NotImplementedError diff --git a/homeassistant/components/ihc/services.yaml b/homeassistant/components/ihc/services.yaml new file mode 100644 index 00000000000000..7b6053eff898ae --- /dev/null +++ b/homeassistant/components/ihc/services.yaml @@ -0,0 +1,26 @@ +# Describes the format for available ihc services + +set_runtime_value_bool: + description: Set a boolean runtime value on the ihc controller + fields: + ihc_id: + description: The integer ihc resource id + value: + description: The boolean value to set + +set_runtime_value_int: + description: Set an integer runtime value on the ihc controller + fields: + ihc_id: + description: The integer ihc resource id + value: + description: The integer value to set + +set_runtime_value_float: + description: Set a float runtime value on the ihc controller + fields: + ihc_id: + description: The integer ihc resource id + value: + description: The float value to set + diff --git a/homeassistant/components/light/ihc.py b/homeassistant/components/light/ihc.py new file mode 100644 index 00000000000000..f23ae77c8b2f87 --- /dev/null +++ b/homeassistant/components/light/ihc.py @@ -0,0 +1,123 @@ +"""IHC light platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.const import CONF_DIMMABLE +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, PLATFORM_SCHEMA, Light) +from homeassistant.const import CONF_ID, CONF_NAME, CONF_LIGHTS +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_LIGHTS, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_DIMMABLE, default=False): cv.boolean, + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ihc lights platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + devices = [] + if discovery_info: + for name, device in discovery_info.items(): + ihc_id = device['ihc_id'] + product_cfg = device['product_cfg'] + product = device['product'] + light = IhcLight(ihc_controller, name, ihc_id, info, + product_cfg[CONF_DIMMABLE], product) + devices.append(light) + else: + lights = config[CONF_LIGHTS] + for light in lights: + ihc_id = light[CONF_ID] + name = light[CONF_NAME] + dimmable = light[CONF_DIMMABLE] + device = IhcLight(ihc_controller, name, ihc_id, info, dimmable) + devices.append(device) + + add_devices(devices) + + +class IhcLight(IHCDevice, Light): + """Representation of a IHC light. + + For dimmable lights, the associated IHC resource should be a light + level (integer). For non dimmable light the IHC resource should be + an on/off (boolean) resource + """ + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + dimmable=False, product: Element=None): + """Initialize the light.""" + super().__init__(ihc_controller, name, ihc_id, info, product) + self._brightness = 0 + self._dimmable = dimmable + self._state = None + + @property + def brightness(self) -> int: + """Return the brightness of this light between 0..255.""" + return self._brightness + + @property + def is_on(self) -> bool: + """Return true if light is on.""" + return self._state + + @property + def supported_features(self): + """Flag supported features.""" + if self._dimmable: + return SUPPORT_BRIGHTNESS + return 0 + + def turn_on(self, **kwargs) -> None: + """Turn the light on.""" + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + else: + brightness = self._brightness + if brightness == 0: + brightness = 255 + + if self._dimmable: + self.ihc_controller.set_runtime_value_int( + self.ihc_id, int(brightness * 100 / 255)) + else: + self.ihc_controller.set_runtime_value_bool(self.ihc_id, True) + + def turn_off(self, **kwargs) -> None: + """Turn the light off.""" + if self._dimmable: + self.ihc_controller.set_runtime_value_int(self.ihc_id, 0) + else: + self.ihc_controller.set_runtime_value_bool(self.ihc_id, False) + + def on_ihc_change(self, ihc_id, value): + """Callback from Ihc notifications.""" + if isinstance(value, bool): + self._dimmable = False + self._state = value != 0 + else: + self._dimmable = True + self._state = value > 0 + if self._state: + self._brightness = int(value * 255 / 100) + self.schedule_update_ha_state() diff --git a/homeassistant/components/sensor/ihc.py b/homeassistant/components/sensor/ihc.py new file mode 100644 index 00000000000000..3ad86e51f9730b --- /dev/null +++ b/homeassistant/components/sensor/ihc.py @@ -0,0 +1,84 @@ +"""IHC sensor platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_ID, CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_SENSORS, + TEMP_CELSIUS) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_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_UNIT_OF_MEASUREMENT, + default=TEMP_CELSIUS): cv.string + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ihc sensor platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + 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 = IHCSensor(ihc_controller, name, ihc_id, info, + product_cfg[CONF_UNIT_OF_MEASUREMENT], + product) + devices.append(sensor) + else: + sensors = config[CONF_SENSORS] + for sensor_cfg in sensors: + ihc_id = sensor_cfg[CONF_ID] + name = sensor_cfg[CONF_NAME] + unit = sensor_cfg[CONF_UNIT_OF_MEASUREMENT] + sensor = IHCSensor(ihc_controller, name, ihc_id, info, unit) + devices.append(sensor) + + add_devices(devices) + + +class IHCSensor(IHCDevice, Entity): + """Implementation of the IHC sensor.""" + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + unit, product: Element=None): + """Initialize the IHC sensor.""" + super().__init__(ihc_controller, name, ihc_id, info, product) + self._state = None + self._unit_of_measurement = unit + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + def on_ihc_change(self, ihc_id, value): + """Callback when ihc resource changes.""" + self._state = value + self.schedule_update_ha_state() diff --git a/homeassistant/components/switch/ihc.py b/homeassistant/components/switch/ihc.py new file mode 100644 index 00000000000000..4bab1378acd16c --- /dev/null +++ b/homeassistant/components/switch/ihc.py @@ -0,0 +1,77 @@ +"""IHC switch platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_ID, CONF_NAME, CONF_SWITCHES +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_SWITCHES, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ihc switch platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + devices = [] + if discovery_info: + for name, device in discovery_info.items(): + ihc_id = device['ihc_id'] + product = device['product'] + switch = IHCSwitch(ihc_controller, name, ihc_id, info, product) + devices.append(switch) + else: + switches = config[CONF_SWITCHES] + for switch in switches: + ihc_id = switch[CONF_ID] + name = switch[CONF_NAME] + sensor = IHCSwitch(ihc_controller, name, ihc_id, info) + devices.append(sensor) + + add_devices(devices) + + +class IHCSwitch(IHCDevice, SwitchDevice): + """IHC Switch.""" + + def __init__(self, ihc_controller, name: str, ihc_id: int, + info: bool, product: Element=None): + """Initialize the IHC switch.""" + super().__init__(ihc_controller, name, ihc_id, product) + self._state = False + + @property + def is_on(self): + """Return true if switch is on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the switch on.""" + self.ihc_controller.set_runtime_value_bool(self.ihc_id, True) + + def turn_off(self, **kwargs): + """Turn the device off.""" + self.ihc_controller.set_runtime_value_bool(self.ihc_id, False) + + def on_ihc_change(self, ihc_id, value): + """Callback when the ihc resource changes.""" + self._state = value + self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index ffa7c4e1cbd781..1bee68301d6233 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -398,6 +398,9 @@ https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0 # homeassistant.components.light.iglo iglo==1.1.3 +# homeassistant.components.ihc +ihcsdk==2.1.1 + # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb influxdb==4.1.1