diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index 77ba00e451d..4a421274a18 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -21,6 +21,9 @@ from .const import ( LOGICOP_PORTS, MOTOR_PORTS, 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, + Pck, Relays, SendKeys, VarAbs, VarRel, VarReset) _LOGGER = logging.getLogger(__name__) @@ -155,6 +158,24 @@ async def async_setup(hass, config): hass.async_create_task( async_load_platform(hass, component, DOMAIN, config[DOMAIN][conf_key], config)) + + # register service calls + for service_name, service in (('output_abs', OutputAbs), + ('output_rel', OutputRel), + ('output_toggle', OutputToggle), + ('relays', Relays), + ('var_abs', VarAbs), + ('var_reset', VarReset), + ('var_rel', VarRel), + ('lock_regulator', LockRegulator), + ('led', Led), + ('send_keys', SendKeys), + ('lock_keys', LockKeys), + ('dyn_text', DynText), + ('pck', Pck)): + hass.services.async_register(DOMAIN, service_name, + service(hass), service.schema) + return True diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index 45dc04a491e..9307fb4d706 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -15,9 +15,20 @@ CONF_DIM_MODE = 'dim_mode' CONF_DIMMABLE = 'dimmable' CONF_TRANSITION = 'transition' CONF_MOTOR = 'motor' +CONF_LOCKABLE = 'lockable' +CONF_VARIABLE = 'variable' +CONF_VALUE = 'value' +CONF_RELVARREF = 'value_reference' CONF_SOURCE = 'source' CONF_SETPOINT = 'setpoint' -CONF_LOCKABLE = 'lockable' +CONF_LED = 'led' +CONF_KEYS = 'keys' +CONF_TIME = 'time' +CONF_TIME_UNIT = 'time_unit' +CONF_TABLE = 'table' +CONF_ROW = 'row' +CONF_TEXT = 'text' +CONF_PCK = 'pck' CONF_CLIMATES = 'climates' CONF_MAX_TEMP = 'max_temp' CONF_MIN_TEMP = 'min_temp' @@ -36,6 +47,8 @@ MOTOR_PORTS = ['MOTOR1', 'MOTOR2', 'MOTOR3', 'MOTOR4'] LED_PORTS = ['LED1', 'LED2', 'LED3', 'LED4', 'LED5', 'LED6', 'LED7', 'LED8', 'LED9', 'LED10', 'LED11', 'LED12'] +LED_STATUS = ['OFF', 'ON', 'BLINK', 'FLICKER'] + LOGICOP_PORTS = ['LOGICOP1', 'LOGICOP2', 'LOGICOP3', 'LOGICOP4'] BINSENSOR_PORTS = ['BINSENSOR1', 'BINSENSOR2', 'BINSENSOR3', 'BINSENSOR4', @@ -70,3 +83,12 @@ VAR_UNITS = ['', 'LCN', 'NATIVE', 'VOLT', 'V', 'AMPERE', 'AMP', 'A', 'DEGREE', '°'] + +RELVARREF = ['CURRENT', 'PROG'] + +SENDKEYCOMMANDS = ['HIT', 'MAKE', 'BREAK', 'DONTSEND'] + +TIME_UNITS = ['SECONDS', 'SECOND', 'SEC', 'S', + 'MINUTES', 'MINUTE', 'MIN', 'M', + 'HOURS', 'HOUR', 'H', + 'DAYS', 'DAY', 'D'] diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 701b6e2436e..d663a6320b1 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -65,3 +65,43 @@ def is_address(value): conn_id = matcher.group('conn_id') return addr, conn_id raise vol.error.Invalid('Not a valid address string.') + + +def is_relays_states_string(states_string): + """Validate the given states string and return states list.""" + if len(states_string) == 8: + states = [] + for state_string in states_string: + if state_string == '1': + state = 'ON' + elif state_string == '0': + state = 'OFF' + elif state_string == 'T': + state = 'TOGGLE' + elif state_string == '-': + state = 'NOCHANGE' + else: + raise vol.error.Invalid('Not a valid relay state string.') + states.append(state) + return states + raise vol.error.Invalid('Wrong length of relay state string.') + + +def is_key_lock_states_string(states_string): + """Validate the given states string and returns states list.""" + if len(states_string) == 8: + states = [] + for state_string in states_string: + if state_string == '1': + state = 'ON' + elif state_string == '0': + state = 'OFF' + elif state_string == 'T': + state = 'TOGGLE' + elif state_string == '-': + state = 'NOCHANGE' + else: + raise vol.error.Invalid('Not a valid key lock state string.') + states.append(state) + return states + raise vol.error.Invalid('Wrong length of key lock state string.') diff --git a/homeassistant/components/lcn/services.py b/homeassistant/components/lcn/services.py new file mode 100755 index 00000000000..78a887a80c1 --- /dev/null +++ b/homeassistant/components/lcn/services.py @@ -0,0 +1,326 @@ +"""Service calls related dependencies for LCN component.""" +import pypck +import voluptuous as vol + +from homeassistant.const import ( + CONF_ADDRESS, CONF_BRIGHTNESS, CONF_STATE, CONF_UNIT_OF_MEASUREMENT) +import homeassistant.helpers.config_validation as cv + +from .const import ( + CONF_CONNECTIONS, CONF_KEYS, CONF_LED, CONF_OUTPUT, CONF_PCK, + CONF_RELVARREF, CONF_ROW, CONF_SETPOINT, CONF_TABLE, CONF_TEXT, CONF_TIME, + CONF_TIME_UNIT, CONF_TRANSITION, CONF_VALUE, CONF_VARIABLE, DATA_LCN, + LED_PORTS, LED_STATUS, OUTPUT_PORTS, RELVARREF, SENDKEYCOMMANDS, SETPOINTS, + THRESHOLDS, TIME_UNITS, VAR_UNITS, VARIABLES) +from .helpers import ( + get_connection, is_address, is_key_lock_states_string, + is_relays_states_string) + + +class LcnServiceCall(): + """Parent class for all LCN service calls.""" + + schema = vol.Schema({ + vol.Required(CONF_ADDRESS): is_address + }) + + def __init__(self, hass): + """Initialize service call.""" + self.connections = hass.data[DATA_LCN][CONF_CONNECTIONS] + + def get_address_connection(self, call): + """Get address connection object.""" + addr, connection_id = call.data[CONF_ADDRESS] + addr = pypck.lcn_addr.LcnAddr(*addr) + if connection_id is None: + connection = self.connections[0] + else: + connection = get_connection(self.connections, connection_id) + + return connection.get_address_conn(addr) + + +class OutputAbs(LcnServiceCall): + """Set absolute brightness of output port in percent.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS)), + vol.Required(CONF_BRIGHTNESS): + vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), + vol.Optional(CONF_TRANSITION, default=0): + vol.All(vol.Coerce(float), vol.Range(min=0., max=486.)) + }) + + def __call__(self, call): + """Execute service call.""" + output = pypck.lcn_defs.OutputPort[call.data[CONF_OUTPUT]] + brightness = call.data[CONF_BRIGHTNESS] + transition = pypck.lcn_defs.time_to_ramp_value( + call.data[CONF_TRANSITION] * 1000) + + address_connection = self.get_address_connection(call) + address_connection.dim_output(output.value, brightness, transition) + + +class OutputRel(LcnServiceCall): + """Set relative brightness of output port in percent.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS)), + vol.Required(CONF_BRIGHTNESS): + vol.All(vol.Coerce(int), vol.Range(min=-100, max=100)) + }) + + def __call__(self, call): + """Execute service call.""" + output = pypck.lcn_defs.OutputPort[call.data[CONF_OUTPUT]] + brightness = call.data[CONF_BRIGHTNESS] + + address_connection = self.get_address_connection(call) + address_connection.rel_output(output.value, brightness) + + +class OutputToggle(LcnServiceCall): + """Toggle output port.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS)), + vol.Optional(CONF_TRANSITION, default=0): + vol.All(vol.Coerce(float), vol.Range(min=0., max=486.)) + }) + + def __call__(self, call): + """Execute service call.""" + output = pypck.lcn_defs.OutputPort[call.data[CONF_OUTPUT]] + transition = pypck.lcn_defs.time_to_ramp_value( + call.data[CONF_TRANSITION] * 1000) + + address_connection = self.get_address_connection(call) + address_connection.toggle_output(output.value, transition) + + +class Relays(LcnServiceCall): + """Set the relays status.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_STATE): is_relays_states_string}) + + def __call__(self, call): + """Execute service call.""" + states = [pypck.lcn_defs.RelayStateModifier[state] + for state in call.data[CONF_STATE]] + + address_connection = self.get_address_connection(call) + address_connection.control_relays(states) + + +class Led(LcnServiceCall): + """Set the led state.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_LED): vol.All(vol.Upper, vol.In(LED_PORTS)), + vol.Required(CONF_STATE): vol.All(vol.Upper, vol.In(LED_STATUS))}) + + def __call__(self, call): + """Execute service call.""" + led = pypck.lcn_defs.LedPort[call.data[CONF_LED]] + led_state = pypck.lcn_defs.LedStatus[ + call.data[CONF_STATE]] + + address_connection = self.get_address_connection(call) + address_connection.control_led(led, led_state) + + +class VarAbs(LcnServiceCall): + """Set absolute value of a variable or setpoint. + + Variable has to be set as counter! + Reguator setpoints can also be set using R1VARSETPOINT, R2VARSETPOINT. + """ + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_VARIABLE): vol.All(vol.Upper, + vol.In(VARIABLES + SETPOINTS)), + vol.Optional(CONF_VALUE, default=0): + vol.All(vol.Coerce(int), vol.Range(min=0)), + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default='native'): + vol.All(vol.Upper, vol.In(VAR_UNITS)) + }) + + def __call__(self, call): + """Execute service call.""" + var = pypck.lcn_defs.Var[call.data[CONF_VARIABLE]] + value = call.data[CONF_VALUE] + unit = pypck.lcn_defs.VarUnit.parse( + call.data[CONF_UNIT_OF_MEASUREMENT]) + + address_connection = self.get_address_connection(call) + address_connection.var_abs(var, value, unit) + + +class VarReset(LcnServiceCall): + """Reset value of variable or setpoint.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_VARIABLE): vol.All(vol.Upper, + vol.In(VARIABLES + SETPOINTS)) + }) + + def __call__(self, call): + """Execute service call.""" + var = pypck.lcn_defs.Var[call.data[CONF_VARIABLE]] + + address_connection = self.get_address_connection(call) + address_connection.var_reset(var) + + +class VarRel(LcnServiceCall): + """Shift value of a variable, setpoint or threshold.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_VARIABLE): + vol.All(vol.Upper, vol.In(VARIABLES + SETPOINTS + THRESHOLDS)), + vol.Optional(CONF_VALUE, default=0): int, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default='native'): + vol.All(vol.Upper, vol.In(VAR_UNITS)), + vol.Optional(CONF_RELVARREF, default='current'): + vol.All(vol.Upper, vol.In(RELVARREF)) + }) + + def __call__(self, call): + """Execute service call.""" + var = pypck.lcn_defs.Var[call.data[CONF_VARIABLE]] + value = call.data[CONF_VALUE] + unit = pypck.lcn_defs.VarUnit.parse( + call.data[CONF_UNIT_OF_MEASUREMENT]) + value_ref = pypck.lcn_defs.RelVarRef[ + call.data[CONF_RELVARREF]] + + address_connection = self.get_address_connection(call) + address_connection.var_rel(var, value, unit, value_ref) + + +class LockRegulator(LcnServiceCall): + """Locks a regulator setpoint.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_SETPOINT): vol.All(vol.Upper, vol.In(SETPOINTS)), + vol.Optional(CONF_STATE, default=False): bool, + }) + + def __call__(self, call): + """Execute service call.""" + setpoint = pypck.lcn_defs.Var[call.data[CONF_SETPOINT]] + state = call.data[CONF_STATE] + + reg_id = pypck.lcn_defs.Var.to_set_point_id(setpoint) + address_connection = self.get_address_connection(call) + address_connection.lock_regulator(reg_id, state) + + +class SendKeys(LcnServiceCall): + """Sends keys (which executes bound commands).""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_KEYS): cv.matches_regex(r'^([a-dA-D][1-8])+$'), + vol.Optional(CONF_STATE, default='hit'): + vol.All(vol.Upper, vol.In(SENDKEYCOMMANDS)), + vol.Optional(CONF_TIME, default=0): vol.All(int, vol.Range(min=0)), + vol.Optional(CONF_TIME_UNIT, default='s'): vol.All(vol.Upper, + vol.In(TIME_UNITS)) + }) + + def __call__(self, call): + """Execute service call.""" + address_connection = self.get_address_connection(call) + + keys = [[False] * 8 for i in range(4)] + + key_strings = zip(call.data[CONF_KEYS][::2], + call.data[CONF_KEYS][1::2]) + + for table, key in key_strings: + table_id = ord(table) - 65 + key_id = int(key) - 1 + keys[table_id][key_id] = True + + delay_time = call.data[CONF_TIME] + if delay_time != 0: + hit = pypck.lcn_defs.SendKeyCommand.HIT + if pypck.lcn_defs.SendKeyCommand[ + call.data[CONF_STATE]] != hit: + raise ValueError('Only hit command is allowed when sending' + ' deferred keys.') + delay_unit = pypck.lcn_defs.TimeUnit.parse( + call.data[CONF_TIME_UNIT]) + address_connection.send_keys_hit_deferred( + keys, delay_time, delay_unit) + else: + state = pypck.lcn_defs.SendKeyCommand[ + call.data[CONF_STATE]] + address_connection.send_keys(keys, state) + + +class LockKeys(LcnServiceCall): + """Lock keys.""" + + schema = LcnServiceCall.schema.extend({ + vol.Optional(CONF_TABLE, default='a'): cv.matches_regex(r'^[a-dA-D]$'), + vol.Required(CONF_STATE): is_key_lock_states_string, + vol.Optional(CONF_TIME, default=0): vol.All(int, vol.Range(min=0)), + vol.Optional(CONF_TIME_UNIT, default='s'): vol.All(vol.Upper, + vol.In(TIME_UNITS)) + }) + + def __call__(self, call): + """Execute service call.""" + address_connection = self.get_address_connection(call) + + states = [pypck.lcn_defs.KeyLockStateModifier[state] + for state in call.data[CONF_STATE]] + table_id = ord(call.data[CONF_TABLE]) - 65 + + delay_time = call.data[CONF_TIME] + if delay_time != 0: + if table_id != 0: + raise ValueError('Only table A is allowed when locking keys' + ' for a specific time.') + delay_unit = pypck.lcn_defs.TimeUnit.parse( + call.data[CONF_TIME_UNIT]) + address_connection.lock_keys_tab_a_temporary( + delay_time, delay_unit, states) + else: + address_connection.lock_keys(table_id, states) + + address_connection.request_status_locked_keys_timeout() + + +class DynText(LcnServiceCall): + """Send dynamic text to LCN-GTxD displays.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_ROW): vol.All(int, vol.Range(min=1, max=4)), + vol.Required(CONF_TEXT): vol.All(str, vol.Length(max=60)) + }) + + def __call__(self, call): + """Execute service call.""" + row_id = call.data[CONF_ROW] - 1 + text = call.data[CONF_TEXT] + + address_connection = self.get_address_connection(call) + address_connection.dyn_text(row_id, text) + + +class Pck(LcnServiceCall): + """Send arbitrary PCK command.""" + + schema = LcnServiceCall.schema.extend({ + vol.Required(CONF_PCK): str + }) + + def __call__(self, call): + """Execute service call.""" + pck = call.data[CONF_PCK] + address_connection = self.get_address_connection(call) + address_connection.pck(pck) diff --git a/homeassistant/components/lcn/services.yaml b/homeassistant/components/lcn/services.yaml new file mode 100755 index 00000000000..b8f4fbb20a7 --- /dev/null +++ b/homeassistant/components/lcn/services.yaml @@ -0,0 +1,201 @@ +# Describes the format for available LCN services + +output_abs: + description: Set absolute brightness of output port in percent. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + output: + description: Output port + example: "output1" + brightness: + description: Absolute brightness in percent (0..100) + example: 50 + transition: + description: Transition time in seconds + example: 5 + +output_rel: + description: Set relative brightness of output port in percent. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + output: + description: Output port + example: "output1" + brightness: + description: Relative brightness in percent (-100..100) + example: 50 + transition: + description: Transition time in seconds + example: 5 + +output_toggle: + description: Toggle output port. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + output: + description: Output port + example: "output1" + transition: + description: Transition time in seconds + example: 5 + +relays: + description: Set the relays status. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + state: + description: Relays states as string (1=on, 2=off, t=toggle, -=nochange) + example: "t---001-" + +led: + description: Set the led state. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + led: + description: Led + example: "led6" + state: + description: Led state + example: 'blink' + values: + - on + - off + - blink + - flicker + +var_abs: + description: Set absolute value of a variable or setpoint. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + variable: + description: Variable or setpoint name + example: 'var1' + value: + description: Value to set + example: '50' + unit_of_measurement: + description: Unit of value + example: 'celsius' + +var_reset: + description: Reset value of variable or setpoint. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + variable: + description: Variable or setpoint name + example: 'var1' + +var_rel: + description: Shift value of a variable, setpoint or threshold. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + variable: + description: Variable or setpoint name + example: 'var1' + value: + description: Shift value + example: '50' + unit_of_measurement: + description: Unit of value + example: 'celsius' + value_reference: + description: Reference value (current or programmed) for setpoint and threshold + example: 'current' + values: + - current + - prog + +lock_regulator: + description: Lock a regulator setpoint. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + setpoint: + description: Setpoint name + example: 'r1varsetpoint' + state: + description: New setpoint state + example: true + +send_keys: + description: Send keys (which executes bound commands). + fields: + address: + description: Module address + example: 'myhome.s0.m7' + keys: + description: Keys to send + example: 'a1a5d8' + state: + description: 'Key state upon sending (optional, must be hit for deferred)' + example: 'hit' + values: + - hit + - make + - break + time: + description: Send delay (optional) + example: 10 + time_unit: + description: Time unit of send delay (optional) + example: 's' + +lock_keys: + description: Lock keys. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + table: + description: 'Table with keys to lock (optional, must be A for interval).' + example: 'A5' + state: + description: Key lock states as string (1=on, 2=off, T=toggle, -=nochange) + example: '1---t0--' + time: + description: Lock interval (optional) + example: 10 + time_unit: + description: Time unit of lock interval (optional) + example: 's' + +dyn_text: + description: Send dynamic text to LCN-GTxD displays. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + row: + description: Text row 1..4 (support of 4 independent text rows) + example: 1 + text: + description: Text to send (up to 60 characters encoded as UTF-8) + example: 'text up to 60 characters' + +pck: + description: Send arbitrary PCK command. + fields: + address: + description: Module address + example: 'myhome.s0.m7' + pck: + description: PCK command (without address header) + example: 'PIN4' + \ No newline at end of file