2019-02-14 04:35:12 +00:00
|
|
|
"""Support for LCN devices."""
|
2018-12-28 11:39:06 +00:00
|
|
|
import logging
|
|
|
|
|
2019-04-24 02:20:20 +00:00
|
|
|
import pypck
|
2018-12-28 11:39:06 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2019-05-11 08:24:02 +00:00
|
|
|
from homeassistant.components.climate import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP
|
2018-12-28 11:39:06 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
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,
|
|
|
|
)
|
2019-12-09 13:22:30 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2018-12-28 11:39:06 +00:00
|
|
|
from homeassistant.helpers.discovery import async_load_platform
|
|
|
|
from homeassistant.helpers.entity import Entity
|
|
|
|
|
2019-03-21 05:56:46 +00:00
|
|
|
from .const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
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_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,
|
|
|
|
)
|
2019-05-25 09:40:44 +00:00
|
|
|
from .helpers import has_unique_connection_names, is_address
|
2019-05-29 22:59:38 +00:00
|
|
|
from .services import (
|
2019-07-31 19:25:30 +00:00
|
|
|
DynText,
|
|
|
|
Led,
|
|
|
|
LockKeys,
|
|
|
|
LockRegulator,
|
|
|
|
OutputAbs,
|
|
|
|
OutputRel,
|
|
|
|
OutputToggle,
|
|
|
|
Pck,
|
|
|
|
Relays,
|
|
|
|
SendKeys,
|
|
|
|
VarAbs,
|
|
|
|
VarRel,
|
|
|
|
VarReset,
|
|
|
|
)
|
2019-03-21 05:56:46 +00:00
|
|
|
|
2018-12-28 11:39:06 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
BINARY_SENSORS_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_NAME): cv.string,
|
|
|
|
vol.Required(CONF_ADDRESS): is_address,
|
|
|
|
vol.Required(CONF_SOURCE): vol.All(
|
|
|
|
vol.Upper, vol.In(SETPOINTS + KEYS + BINSENSOR_PORTS)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
CLIMATES_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_NAME): cv.string,
|
|
|
|
vol.Required(CONF_ADDRESS): is_address,
|
|
|
|
vol.Required(CONF_SOURCE): vol.All(vol.Upper, vol.In(VARIABLES)),
|
|
|
|
vol.Required(CONF_SETPOINT): vol.All(vol.Upper, vol.In(VARIABLES + SETPOINTS)),
|
|
|
|
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
|
|
|
|
vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
|
|
|
|
vol.Optional(CONF_LOCKABLE, default=False): vol.Coerce(bool),
|
|
|
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=TEMP_CELSIUS): vol.In(
|
|
|
|
TEMP_CELSIUS, TEMP_FAHRENHEIT
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
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.Optional(CONF_REVERSE_TIME): vol.All(vol.Upper, vol.In(MOTOR_REVERSE_TIME)),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
LIGHTS_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_NAME): cv.string,
|
|
|
|
vol.Required(CONF_ADDRESS): is_address,
|
|
|
|
vol.Required(CONF_OUTPUT): vol.All(
|
|
|
|
vol.Upper, vol.In(OUTPUT_PORTS + RELAY_PORTS)
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_DIMMABLE, default=False): vol.Coerce(bool),
|
|
|
|
vol.Optional(CONF_TRANSITION, default=0): vol.All(
|
|
|
|
vol.Coerce(float), vol.Range(min=0.0, max=486.0), lambda value: value * 1000
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
SCENES_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_NAME): cv.string,
|
|
|
|
vol.Required(CONF_ADDRESS): is_address,
|
|
|
|
vol.Required(CONF_REGISTER): vol.All(vol.Coerce(int), vol.Range(0, 9)),
|
|
|
|
vol.Required(CONF_SCENE): vol.All(vol.Coerce(int), vol.Range(0, 9)),
|
|
|
|
vol.Optional(CONF_OUTPUTS): vol.All(
|
|
|
|
cv.ensure_list, [vol.All(vol.Upper, vol.In(OUTPUT_PORTS + RELAY_PORTS))]
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_TRANSITION, default=None): vol.Any(
|
|
|
|
vol.All(
|
|
|
|
vol.Coerce(int),
|
|
|
|
vol.Range(min=0.0, max=486.0),
|
|
|
|
lambda value: value * 1000,
|
|
|
|
),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
SENSORS_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_NAME): cv.string,
|
|
|
|
vol.Required(CONF_ADDRESS): is_address,
|
|
|
|
vol.Required(CONF_SOURCE): vol.All(
|
|
|
|
vol.Upper,
|
|
|
|
vol.In(
|
|
|
|
VARIABLES
|
|
|
|
+ SETPOINTS
|
|
|
|
+ THRESHOLDS
|
|
|
|
+ S0_INPUTS
|
|
|
|
+ LED_PORTS
|
|
|
|
+ LOGICOP_PORTS
|
|
|
|
),
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default="native"): vol.All(
|
|
|
|
vol.Upper, vol.In(VAR_UNITS)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
SWITCHES_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_NAME): cv.string,
|
|
|
|
vol.Required(CONF_ADDRESS): is_address,
|
|
|
|
vol.Required(CONF_OUTPUT): vol.All(
|
|
|
|
vol.Upper, vol.In(OUTPUT_PORTS + RELAY_PORTS)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
CONNECTION_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Required(CONF_PORT): cv.port,
|
|
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
|
|
vol.Optional(CONF_SK_NUM_TRIES, default=0): cv.positive_int,
|
|
|
|
vol.Optional(CONF_DIM_MODE, default="steps50"): vol.All(
|
|
|
|
vol.Upper, vol.In(DIM_MODES)
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_NAME): cv.string,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
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_BINARY_SENSORS): vol.All(
|
|
|
|
cv.ensure_list, [BINARY_SENSORS_SCHEMA]
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_CLIMATES): vol.All(cv.ensure_list, [CLIMATES_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_SCENES): vol.All(cv.ensure_list, [SCENES_SCHEMA]),
|
|
|
|
vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSORS_SCHEMA]),
|
|
|
|
vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCHES_SCHEMA]),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
2018-12-28 11:39:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup(hass, config):
|
|
|
|
"""Set up the LCN component."""
|
|
|
|
hass.data[DATA_LCN] = {}
|
|
|
|
|
|
|
|
conf_connections = config[DOMAIN][CONF_CONNECTIONS]
|
|
|
|
connections = []
|
|
|
|
for conf_connection in conf_connections:
|
|
|
|
connection_name = conf_connection.get(CONF_NAME)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
settings = {
|
|
|
|
"SK_NUM_TRIES": conf_connection[CONF_SK_NUM_TRIES],
|
|
|
|
"DIM_MODE": pypck.lcn_defs.OutputPortDimMode[
|
|
|
|
conf_connection[CONF_DIM_MODE]
|
|
|
|
],
|
|
|
|
}
|
2018-12-28 11:39:06 +00:00
|
|
|
|
2019-05-25 09:40:44 +00:00
|
|
|
connection = pypck.connection.PchkConnectionManager(
|
|
|
|
hass.loop,
|
|
|
|
conf_connection[CONF_HOST],
|
|
|
|
conf_connection[CONF_PORT],
|
|
|
|
conf_connection[CONF_USERNAME],
|
|
|
|
conf_connection[CONF_PASSWORD],
|
|
|
|
settings=settings,
|
2019-07-31 19:25:30 +00:00
|
|
|
connection_id=connection_name,
|
|
|
|
)
|
2018-12-28 11:39:06 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
# establish connection to PCHK server
|
|
|
|
await hass.async_create_task(connection.async_connect(timeout=15))
|
|
|
|
connections.append(connection)
|
|
|
|
_LOGGER.info('LCN connected to "%s"', connection_name)
|
|
|
|
except TimeoutError:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error('Connection to PCHK server "%s" failed.', connection_name)
|
2018-12-28 11:39:06 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
hass.data[DATA_LCN][CONF_CONNECTIONS] = connections
|
|
|
|
|
2019-02-23 09:13:15 +00:00
|
|
|
# load platforms
|
2019-07-31 19:25:30 +00:00
|
|
|
for component, conf_key in (
|
|
|
|
("binary_sensor", CONF_BINARY_SENSORS),
|
|
|
|
("climate", CONF_CLIMATES),
|
|
|
|
("cover", CONF_COVERS),
|
|
|
|
("light", CONF_LIGHTS),
|
|
|
|
("scene", CONF_SCENES),
|
|
|
|
("sensor", CONF_SENSORS),
|
|
|
|
("switch", CONF_SWITCHES),
|
|
|
|
):
|
2019-02-23 14:58:18 +00:00
|
|
|
if conf_key in config[DOMAIN]:
|
|
|
|
hass.async_create_task(
|
2019-07-31 19:25:30 +00:00
|
|
|
async_load_platform(
|
|
|
|
hass, component, DOMAIN, config[DOMAIN][conf_key], config
|
|
|
|
)
|
|
|
|
)
|
2019-05-29 22:59:38 +00:00
|
|
|
|
|
|
|
# register service calls
|
2019-07-31 19:25:30 +00:00
|
|
|
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
|
|
|
|
)
|
2019-05-29 22:59:38 +00:00
|
|
|
|
2018-12-28 11:39:06 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class LcnDevice(Entity):
|
|
|
|
"""Parent class for all devices associated with the LCN component."""
|
|
|
|
|
|
|
|
def __init__(self, config, address_connection):
|
|
|
|
"""Initialize the LCN device."""
|
|
|
|
self.config = config
|
|
|
|
self.address_connection = address_connection
|
|
|
|
self._name = config[CONF_NAME]
|
|
|
|
|
|
|
|
@property
|
2019-01-20 13:50:29 +00:00
|
|
|
def should_poll(self):
|
2018-12-28 11:39:06 +00:00
|
|
|
"""Lcn device entity pushes its state to HA."""
|
|
|
|
return False
|
|
|
|
|
2019-01-20 13:50:29 +00:00
|
|
|
async def async_added_to_hass(self):
|
2018-12-28 11:39:06 +00:00
|
|
|
"""Run when entity about to be added to hass."""
|
2019-07-31 19:25:30 +00:00
|
|
|
self.address_connection.register_for_inputs(self.input_received)
|
2018-12-28 11:39:06 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the device."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
def input_received(self, input_obj):
|
|
|
|
"""Set state/value when LCN input object (command) is received."""
|
2019-07-31 19:25:30 +00:00
|
|
|
raise NotImplementedError("Pure virtual function.")
|