240 lines
8.3 KiB
Python
240 lines
8.3 KiB
Python
|
"""Support for Rflink lights.
|
||
|
|
||
|
For more details about this platform, please refer to the documentation
|
||
|
at https://home-assistant.io/components/light.rflink/
|
||
|
|
||
|
"""
|
||
|
import asyncio
|
||
|
import logging
|
||
|
|
||
|
from homeassistant.components import group
|
||
|
from homeassistant.components.light import (
|
||
|
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
|
||
|
from homeassistant.components.rflink import (
|
||
|
CONF_ALIASSES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT,
|
||
|
CONF_IGNORE_DEVICES, CONF_NEW_DEVICES_GROUP, CONF_SIGNAL_REPETITIONS,
|
||
|
DATA_DEVICE_REGISTER, DATA_ENTITY_LOOKUP, DEFAULT_SIGNAL_REPETITIONS,
|
||
|
DOMAIN, EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, cv, vol)
|
||
|
from homeassistant.const import CONF_NAME, CONF_PLATFORM, CONF_TYPE
|
||
|
|
||
|
DEPENDENCIES = ['rflink']
|
||
|
|
||
|
_LOGGER = logging.getLogger(__name__)
|
||
|
|
||
|
TYPE_DIMMABLE = 'dimmable'
|
||
|
TYPE_SWITCHABLE = 'switchable'
|
||
|
TYPE_HYBRID = 'hybrid'
|
||
|
|
||
|
DEVICE_DEFAULTS_SCHEMA = vol.Schema({
|
||
|
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
||
|
vol.Optional(CONF_SIGNAL_REPETITIONS,
|
||
|
default=DEFAULT_SIGNAL_REPETITIONS): vol.Coerce(int),
|
||
|
})
|
||
|
PLATFORM_SCHEMA = vol.Schema({
|
||
|
vol.Required(CONF_PLATFORM): DOMAIN,
|
||
|
vol.Optional(CONF_NEW_DEVICES_GROUP, default=None): cv.string,
|
||
|
vol.Optional(CONF_IGNORE_DEVICES): vol.All(cv.ensure_list, [cv.string]),
|
||
|
vol.Optional(CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})):
|
||
|
DEVICE_DEFAULTS_SCHEMA,
|
||
|
vol.Optional(CONF_DEVICES, default={}): vol.Schema({
|
||
|
cv.string: {
|
||
|
vol.Optional(CONF_NAME): cv.string,
|
||
|
vol.Optional(CONF_TYPE):
|
||
|
vol.Any(TYPE_DIMMABLE, TYPE_SWITCHABLE, TYPE_HYBRID),
|
||
|
vol.Optional(CONF_ALIASSES, default=[]):
|
||
|
vol.All(cv.ensure_list, [cv.string]),
|
||
|
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
||
|
vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int),
|
||
|
},
|
||
|
}),
|
||
|
})
|
||
|
|
||
|
|
||
|
def entity_type_for_device_id(device_id):
|
||
|
"""Return entity class for procotol of a given device_id.
|
||
|
|
||
|
Async friendly.
|
||
|
|
||
|
"""
|
||
|
entity_type_mapping = {
|
||
|
# KlikAanKlikUit support both dimmers and on/off switches on the same
|
||
|
# protocol
|
||
|
'newkaku': TYPE_HYBRID,
|
||
|
}
|
||
|
protocol = device_id.split('_')[0]
|
||
|
return entity_type_mapping.get(protocol, None)
|
||
|
|
||
|
|
||
|
def entity_class_for_type(entity_type):
|
||
|
"""Translate entity type to entity class.
|
||
|
|
||
|
Async friendly.
|
||
|
|
||
|
"""
|
||
|
entity_device_mapping = {
|
||
|
# sends only 'dim' commands not compatible with on/off switches
|
||
|
TYPE_DIMMABLE: DimmableRflinkLight,
|
||
|
# sends only 'on/off' commands not advices with dimmers and signal
|
||
|
# repetition
|
||
|
TYPE_SWITCHABLE: RflinkLight,
|
||
|
# sends 'dim' and 'on' command to support both dimmers and on/off
|
||
|
# switches. Not compatible with signal repetition.
|
||
|
TYPE_HYBRID: HybridRflinkLight,
|
||
|
}
|
||
|
|
||
|
return entity_device_mapping.get(entity_type, RflinkLight)
|
||
|
|
||
|
|
||
|
def devices_from_config(domain_config, hass=None):
|
||
|
"""Parse config and add rflink switch devices."""
|
||
|
devices = []
|
||
|
for device_id, config in domain_config[CONF_DEVICES].items():
|
||
|
# determine which kind of entity to create
|
||
|
if CONF_TYPE in config:
|
||
|
# remove type from config to not pass it as and argument to entity
|
||
|
# instantiation
|
||
|
entity_type = config.pop(CONF_TYPE)
|
||
|
else:
|
||
|
entity_type = entity_type_for_device_id(device_id)
|
||
|
entity_class = entity_class_for_type(entity_type)
|
||
|
|
||
|
device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config)
|
||
|
|
||
|
is_hybrid = entity_class is HybridRflinkLight
|
||
|
|
||
|
# make user aware this can cause problems
|
||
|
repetitions_enabled = device_config[CONF_SIGNAL_REPETITIONS] != 1
|
||
|
if is_hybrid and repetitions_enabled:
|
||
|
_LOGGER.warning(
|
||
|
"Hybrid type for %s not compatible with signal "
|
||
|
"repetitions. Please set 'dimmable' or 'switchable' "
|
||
|
"type explicity in configuration.",
|
||
|
device_id)
|
||
|
|
||
|
device = entity_class(device_id, hass, **device_config)
|
||
|
devices.append(device)
|
||
|
|
||
|
# register entity (and aliasses) to listen to incoming rflink events
|
||
|
for _id in [device_id] + config[CONF_ALIASSES]:
|
||
|
hass.data[DATA_ENTITY_LOOKUP][
|
||
|
EVENT_KEY_COMMAND][_id].append(device)
|
||
|
|
||
|
return devices
|
||
|
|
||
|
|
||
|
@asyncio.coroutine
|
||
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||
|
"""Setup the Rflink platform."""
|
||
|
# add devices from config
|
||
|
yield from async_add_devices(devices_from_config(config, hass))
|
||
|
|
||
|
# add new (unconfigured) devices to user desired group
|
||
|
if config[CONF_NEW_DEVICES_GROUP]:
|
||
|
new_devices_group = yield from group.Group.async_create_group(
|
||
|
hass, config[CONF_NEW_DEVICES_GROUP], [], True)
|
||
|
else:
|
||
|
new_devices_group = None
|
||
|
|
||
|
@asyncio.coroutine
|
||
|
def add_new_device(event):
|
||
|
"""Check if device is known, otherwise add to list of known devices."""
|
||
|
device_id = event[EVENT_KEY_ID]
|
||
|
|
||
|
entity_type = entity_type_for_device_id(event[EVENT_KEY_ID])
|
||
|
entity_class = entity_class_for_type(entity_type)
|
||
|
|
||
|
device_config = config[CONF_DEVICE_DEFAULTS]
|
||
|
device = entity_class(device_id, hass, **device_config)
|
||
|
yield from async_add_devices([device])
|
||
|
|
||
|
# register entity to listen to incoming rflink events
|
||
|
hass.data[DATA_ENTITY_LOOKUP][
|
||
|
EVENT_KEY_COMMAND][device_id].append(device)
|
||
|
|
||
|
# make sure the event is processed by the new entity
|
||
|
device.handle_event(event)
|
||
|
|
||
|
# maybe add to new devices group
|
||
|
if new_devices_group:
|
||
|
yield from new_devices_group.async_update_tracked_entity_ids(
|
||
|
list(new_devices_group.tracking) + [device.entity_id])
|
||
|
|
||
|
hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_COMMAND] = add_new_device
|
||
|
|
||
|
|
||
|
class RflinkLight(SwitchableRflinkDevice, Light):
|
||
|
"""Representation of a Rflink light."""
|
||
|
|
||
|
pass
|
||
|
|
||
|
|
||
|
class DimmableRflinkLight(SwitchableRflinkDevice, Light):
|
||
|
"""Rflink light device that support dimming."""
|
||
|
|
||
|
_brightness = 255
|
||
|
|
||
|
@asyncio.coroutine
|
||
|
def async_turn_on(self, **kwargs):
|
||
|
"""Turn the device on."""
|
||
|
if ATTR_BRIGHTNESS in kwargs:
|
||
|
# rflink only support 16 brightness levels
|
||
|
self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17
|
||
|
|
||
|
# turn on light at the requested dim level
|
||
|
yield from self._async_handle_command('dim', self._brightness)
|
||
|
|
||
|
@property
|
||
|
def brightness(self):
|
||
|
"""Return the brightness of this light between 0..255."""
|
||
|
return self._brightness
|
||
|
|
||
|
@property
|
||
|
def supported_features(self):
|
||
|
"""Flag supported features."""
|
||
|
return SUPPORT_BRIGHTNESS
|
||
|
|
||
|
|
||
|
class HybridRflinkLight(SwitchableRflinkDevice, Light):
|
||
|
"""Rflink light device that sends out both dim and on/off commands.
|
||
|
|
||
|
Used for protocols which support lights that are not exclusively on/off
|
||
|
style. For example KlikAanKlikUit supports both on/off and dimmable light
|
||
|
switches using the same protocol. This type allows unconfigured
|
||
|
KlikAanKlikUit devices to support dimming without breaking support for
|
||
|
on/off switches.
|
||
|
|
||
|
This type is not compatible with signal repetitions as the 'dim' and 'on'
|
||
|
command are send sequential and multiple 'on' commands to a dimmable
|
||
|
device can cause the dimmer to switch into a pulsating brightness mode.
|
||
|
Which results in a nice house disco :)
|
||
|
|
||
|
"""
|
||
|
|
||
|
_brightness = 255
|
||
|
|
||
|
@asyncio.coroutine
|
||
|
def async_turn_on(self, **kwargs):
|
||
|
"""Turn the device on and set dim level."""
|
||
|
if ATTR_BRIGHTNESS in kwargs:
|
||
|
# rflink only support 16 brightness levels
|
||
|
self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17
|
||
|
|
||
|
# if receiver supports dimming this will turn on the light
|
||
|
# at the requested dim level
|
||
|
yield from self._async_handle_command('dim', self._brightness)
|
||
|
|
||
|
# if the receiving device does not support dimlevel this
|
||
|
# will ensure it is turned on when full brightness is set
|
||
|
if self._brightness == 255:
|
||
|
yield from self._async_handle_command("turn_on")
|
||
|
|
||
|
@property
|
||
|
def brightness(self):
|
||
|
"""Return the brightness of this light between 0..255."""
|
||
|
return self._brightness
|
||
|
|
||
|
@property
|
||
|
def supported_features(self):
|
||
|
"""Flag supported features."""
|
||
|
return SUPPORT_BRIGHTNESS
|