From 8f70c168639aba2db682b0478eb9a4028c862a57 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Sat, 23 Feb 2019 10:13:15 +0100 Subject: [PATCH] Add LCN cover platform (#20288) * Add LCN cover platform * Removed unused default value * Moved cover component to lcn platform directory. Small changes due to change request * Closed state is set before updating --- homeassistant/components/lcn/__init__.py | 38 ++++++---- homeassistant/components/lcn/cover.py | 89 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 12 deletions(-) create mode 100755 homeassistant/components/lcn/cover.py diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index 941160b6397..6fb9f58a1d6 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -5,8 +5,8 @@ import re import voluptuous as vol from homeassistant.const import ( - CONF_ADDRESS, CONF_HOST, CONF_LIGHTS, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_SWITCHES, CONF_USERNAME) + CONF_ADDRESS, CONF_COVERS, CONF_HOST, CONF_LIGHTS, CONF_NAME, + CONF_PASSWORD, CONF_PORT, CONF_SWITCHES, CONF_USERNAME) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.entity import Entity @@ -25,11 +25,15 @@ CONF_OUTPUT = 'output' CONF_TRANSITION = 'transition' CONF_DIMMABLE = 'dimmable' CONF_CONNECTIONS = 'connections' +CONF_MOTOR = 'motor' DIM_MODES = ['STEPS50', 'STEPS200'] OUTPUT_PORTS = ['OUTPUT1', 'OUTPUT2', 'OUTPUT3', 'OUTPUT4'] RELAY_PORTS = ['RELAY1', 'RELAY2', 'RELAY3', 'RELAY4', - 'RELAY5', 'RELAY6', 'RELAY7', 'RELAY8'] + 'RELAY5', 'RELAY6', 'RELAY7', 'RELAY8', + 'MOTORONOFF1', 'MOTORUPDOWN1', 'MOTORONOFF2', 'MOTORUPDOWN2', + 'MOTORONOFF3', 'MOTORUPDOWN3', 'MOTORONOFF4', 'MOTORUPDOWN4'] +MOTOR_PORTS = ['MOTOR1', 'MOTOR2', 'MOTOR3', 'MOTOR4'] # Regex for address validation PATTERN_ADDRESS = re.compile('^((?P\\w+)\\.)?s?(?P\\d+)' @@ -78,6 +82,12 @@ def is_address(value): raise vol.error.Invalid('Not a valid address string.') +COVERS_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_ADDRESS): is_address, + vol.Required(CONF_MOTOR): vol.All(vol.Upper, vol.In(MOTOR_PORTS)) + }) + LIGHTS_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.string, vol.Required(CONF_ADDRESS): is_address, @@ -111,8 +121,12 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_CONNECTIONS): vol.All( cv.ensure_list, has_unique_connection_names, [CONNECTION_SCHEMA]), - vol.Optional(CONF_LIGHTS): vol.All(cv.ensure_list, [LIGHTS_SCHEMA]), - vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCHES_SCHEMA]) + vol.Optional(CONF_COVERS): vol.All( + cv.ensure_list, [COVERS_SCHEMA]), + vol.Optional(CONF_LIGHTS): vol.All( + cv.ensure_list, [LIGHTS_SCHEMA]), + vol.Optional(CONF_SWITCHES): vol.All( + cv.ensure_list, [SWITCHES_SCHEMA]) }) }, extra=vol.ALLOW_EXTRA) @@ -166,13 +180,13 @@ async def async_setup(hass, config): hass.data[DATA_LCN][CONF_CONNECTIONS] = connections - hass.async_create_task( - async_load_platform(hass, 'light', DOMAIN, - config[DOMAIN][CONF_LIGHTS], config)) - - hass.async_create_task( - async_load_platform(hass, 'switch', DOMAIN, - config[DOMAIN][CONF_SWITCHES], config)) + # load platforms + for component, conf_key in (('cover', CONF_COVERS), + ('light', CONF_LIGHTS), + ('switch', CONF_SWITCHES)): + hass.async_create_task( + async_load_platform(hass, component, DOMAIN, + config[DOMAIN][conf_key], config)) return True diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py new file mode 100755 index 00000000000..8151144f077 --- /dev/null +++ b/homeassistant/components/lcn/cover.py @@ -0,0 +1,89 @@ +"""Support for LCN covers.""" +from homeassistant.components.cover import CoverDevice +from homeassistant.components.lcn import ( + CONF_CONNECTIONS, CONF_MOTOR, DATA_LCN, LcnDevice, get_connection) +from homeassistant.const import CONF_ADDRESS + +DEPENDENCIES = ['lcn'] + + +async def async_setup_platform(hass, hass_config, async_add_entities, + discovery_info=None): + """Setups the LCN cover platform.""" + if discovery_info is None: + return + + import pypck + + devices = [] + for config in discovery_info: + address, connection_id = config[CONF_ADDRESS] + addr = pypck.lcn_addr.LcnAddr(*address) + connections = hass.data[DATA_LCN][CONF_CONNECTIONS] + connection = get_connection(connections, connection_id) + address_connection = connection.get_address_conn(addr) + + devices.append(LcnCover(config, address_connection)) + + async_add_entities(devices) + + +class LcnCover(LcnDevice, CoverDevice): + """Representation of a LCN cover.""" + + def __init__(self, config, address_connection): + """Initialize the LCN cover.""" + super().__init__(config, address_connection) + + self.motor = self.pypck.lcn_defs.MotorPort[config[CONF_MOTOR]] + self.motor_port_onoff = self.motor.value * 2 + self.motor_port_updown = self.motor_port_onoff + 1 + + self._closed = None + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + await super().async_added_to_hass() + self.hass.async_create_task( + self.address_connection.activate_status_request_handler( + self.motor)) + + @property + def is_closed(self): + """Return if the cover is closed.""" + return self._closed + + async def async_close_cover(self, **kwargs): + """Close the cover.""" + self._closed = True + states = [self.pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 + states[self.motor.value] = self.pypck.lcn_defs.MotorStateModifier.DOWN + self.address_connection.control_motors(states) + await self.async_update_ha_state() + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + self._closed = False + states = [self.pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 + states[self.motor.value] = self.pypck.lcn_defs.MotorStateModifier.UP + self.address_connection.control_motors(states) + await self.async_update_ha_state() + + async def async_stop_cover(self, **kwargs): + """Stop the cover.""" + self._closed = None + states = [self.pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 + states[self.motor.value] = self.pypck.lcn_defs.MotorStateModifier.STOP + self.address_connection.control_motors(states) + await self.async_update_ha_state() + + def input_received(self, input_obj): + """Set cover states when LCN input object (command) is received.""" + if not isinstance(input_obj, self.pypck.inputs.ModStatusRelays): + return + + states = input_obj.states # list of boolean values (relay on/off) + if states[self.motor_port_onoff]: # motor is on + self._closed = states[self.motor_port_updown] # set direction + + self.async_schedule_update_ha_state()