From c4f673c8946dbc595659c908338ba903b78c8e3e Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Mon, 29 Jul 2019 20:49:44 +0200 Subject: [PATCH] LCN cover control via output ports (#25511) * LCN motor control via oputput ports * Remove default value from cover validator --- homeassistant/components/lcn/__init__.py | 16 ++-- homeassistant/components/lcn/const.py | 5 +- homeassistant/components/lcn/cover.py | 92 ++++++++++++++++++++-- homeassistant/components/lcn/manifest.json | 2 +- requirements_all.txt | 2 +- 5 files changed, 100 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index dcb0d78f5a2..b390a334caa 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -4,23 +4,23 @@ import logging import pypck import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.climate import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP from homeassistant.const import ( CONF_ADDRESS, CONF_BINARY_SENSORS, CONF_COVERS, CONF_HOST, CONF_LIGHTS, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SENSORS, CONF_SWITCHES, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT) -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.entity import Entity from .const import ( BINSENSOR_PORTS, CONF_CLIMATES, CONF_CONNECTIONS, CONF_DIM_MODE, CONF_DIMMABLE, CONF_LOCKABLE, CONF_MAX_TEMP, CONF_MIN_TEMP, CONF_MOTOR, - CONF_OUTPUT, CONF_OUTPUTS, CONF_REGISTER, CONF_SCENE, CONF_SCENES, - CONF_SETPOINT, CONF_SK_NUM_TRIES, CONF_SOURCE, CONF_TRANSITION, DATA_LCN, - DIM_MODES, DOMAIN, KEYS, LED_PORTS, LOGICOP_PORTS, MOTOR_PORTS, - OUTPUT_PORTS, RELAY_PORTS, S0_INPUTS, SETPOINTS, THRESHOLDS, VAR_UNITS, - VARIABLES) + CONF_OUTPUT, CONF_OUTPUTS, CONF_REGISTER, CONF_REVERSE_TIME, CONF_SCENE, + CONF_SCENES, CONF_SETPOINT, CONF_SK_NUM_TRIES, CONF_SOURCE, + CONF_TRANSITION, DATA_LCN, DIM_MODES, DOMAIN, KEYS, LED_PORTS, + LOGICOP_PORTS, MOTOR_PORTS, MOTOR_REVERSE_TIME, OUTPUT_PORTS, RELAY_PORTS, + S0_INPUTS, SETPOINTS, THRESHOLDS, VAR_UNITS, VARIABLES) from .helpers import has_unique_connection_names, is_address from .services import ( DynText, Led, LockKeys, LockRegulator, OutputAbs, OutputRel, OutputToggle, @@ -51,7 +51,9 @@ CLIMATES_SCHEMA = vol.Schema({ 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)) + vol.Required(CONF_MOTOR): vol.All(vol.Upper, vol.In(MOTOR_PORTS)), + vol.Optional(CONF_REVERSE_TIME): vol.All(vol.Upper, + vol.In(MOTOR_REVERSE_TIME)) }) LIGHTS_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index 1cf88851456..72e6f7a6d13 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -36,6 +36,7 @@ CONF_SCENES = 'scenes' CONF_REGISTER = 'register' CONF_SCENE = 'scene' CONF_OUTPUTS = 'outputs' +CONF_REVERSE_TIME = 'reverse_time' DIM_MODES = ['STEPS50', 'STEPS200'] @@ -46,7 +47,7 @@ RELAY_PORTS = ['RELAY1', 'RELAY2', 'RELAY3', 'RELAY4', 'MOTORONOFF1', 'MOTORUPDOWN1', 'MOTORONOFF2', 'MOTORUPDOWN2', 'MOTORONOFF3', 'MOTORUPDOWN3', 'MOTORONOFF4', 'MOTORUPDOWN4'] -MOTOR_PORTS = ['MOTOR1', 'MOTOR2', 'MOTOR3', 'MOTOR4'] +MOTOR_PORTS = ['MOTOR1', 'MOTOR2', 'MOTOR3', 'MOTOR4', 'OUTPUTS'] LED_PORTS = ['LED1', 'LED2', 'LED3', 'LED4', 'LED5', 'LED6', 'LED7', 'LED8', 'LED9', 'LED10', 'LED11', 'LED12'] @@ -96,3 +97,5 @@ TIME_UNITS = ['SECONDS', 'SECOND', 'SEC', 'S', 'MINUTES', 'MINUTE', 'MIN', 'M', 'HOURS', 'HOUR', 'H', 'DAYS', 'DAY', 'D'] + +MOTOR_REVERSE_TIME = ['RT70', 'RT600', 'RT1200'] diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 8b268aa617e..a19e2328b30 100755 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -5,7 +5,7 @@ from homeassistant.components.cover import CoverDevice from homeassistant.const import CONF_ADDRESS from . import LcnDevice -from .const import CONF_CONNECTIONS, CONF_MOTOR, DATA_LCN +from .const import CONF_CONNECTIONS, CONF_MOTOR, CONF_REVERSE_TIME, DATA_LCN from .helpers import get_connection @@ -23,13 +23,91 @@ async def async_setup_platform(hass, hass_config, async_add_entities, connection = get_connection(connections, connection_id) address_connection = connection.get_address_conn(addr) - devices.append(LcnCover(config, address_connection)) + if config[CONF_MOTOR] == 'OUTPUTS': + devices.append(LcnOutputsCover(config, address_connection)) + else: # RELAYS + devices.append(LcnRelayCover(config, address_connection)) async_add_entities(devices) -class LcnCover(LcnDevice, CoverDevice): - """Representation of a LCN cover.""" +class LcnOutputsCover(LcnDevice, CoverDevice): + """Representation of a LCN cover connected to output ports.""" + + def __init__(self, config, address_connection): + """Initialize the LCN cover.""" + super().__init__(config, address_connection) + + self.output_ids = [pypck.lcn_defs.OutputPort['OUTPUTUP'].value, + pypck.lcn_defs.OutputPort['OUTPUTDOWN'].value] + if CONF_REVERSE_TIME in config: + self.reverse_time = pypck.lcn_defs.MotorReverseTime[ + config[CONF_REVERSE_TIME]] + else: + self.reverse_time = None + self._closed = None + self.state_up = False + self.state_down = False + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + await super().async_added_to_hass() + await self.address_connection.activate_status_request_handler( + pypck.lcn_defs.OutputPort['OUTPUTUP']) + await self.address_connection.activate_status_request_handler( + pypck.lcn_defs.OutputPort['OUTPUTDOWN']) + + @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 + + state = pypck.lcn_defs.MotorStateModifier.DOWN + self.address_connection.control_motors_outputs( + state) + await self.async_update_ha_state() + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + self._closed = False + state = pypck.lcn_defs.MotorStateModifier.UP + self.address_connection.control_motors_outputs( + state, self.reverse_time) + await self.async_update_ha_state() + + async def async_stop_cover(self, **kwargs): + """Stop the cover.""" + self._closed = None + state = pypck.lcn_defs.MotorStateModifier.STOP + self.address_connection.control_motors_outputs( + state, self.reverse_time) + 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, pypck.inputs.ModStatusOutput) or \ + input_obj.get_output_id() not in self.output_ids: + return + + if input_obj.get_output_id() == self.output_ids[0]: + self.state_up = (input_obj.get_percent() > 0) + else: # self.output_ids[1] + self.state_down = (input_obj.get_percent() > 0) + + if self.state_up and not self.state_down: + self._closed = False # Cover open + elif self.state_down and not self.state_up: + self._closed = True # Cover closed + + self.async_schedule_update_ha_state() + + +class LcnRelayCover(LcnDevice, CoverDevice): + """Representation of a LCN cover connected to relays.""" def __init__(self, config, address_connection): """Initialize the LCN cover.""" @@ -57,7 +135,7 @@ class LcnCover(LcnDevice, CoverDevice): self._closed = True states = [pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 states[self.motor.value] = pypck.lcn_defs.MotorStateModifier.DOWN - self.address_connection.control_motors(states) + self.address_connection.control_motors_relays(states) await self.async_update_ha_state() async def async_open_cover(self, **kwargs): @@ -65,7 +143,7 @@ class LcnCover(LcnDevice, CoverDevice): self._closed = False states = [pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 states[self.motor.value] = pypck.lcn_defs.MotorStateModifier.UP - self.address_connection.control_motors(states) + self.address_connection.control_motors_relays(states) await self.async_update_ha_state() async def async_stop_cover(self, **kwargs): @@ -73,7 +151,7 @@ class LcnCover(LcnDevice, CoverDevice): self._closed = None states = [pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 states[self.motor.value] = pypck.lcn_defs.MotorStateModifier.STOP - self.address_connection.control_motors(states) + self.address_connection.control_motors_relays(states) await self.async_update_ha_state() def input_received(self, input_obj): diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index 5ff9e763646..5a85b6673f2 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -3,7 +3,7 @@ "name": "Lcn", "documentation": "https://www.home-assistant.io/components/lcn", "requirements": [ - "pypck==0.6.2" + "pypck==0.6.3" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 3fc490c4153..129b0d10173 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1330,7 +1330,7 @@ pyowm==2.10.0 pypca==0.0.4 # homeassistant.components.lcn -pypck==0.6.2 +pypck==0.6.3 # homeassistant.components.pjlink pypjlink2==1.2.0