KNX Component: Scene support and expose sensor values (#11978)

* XKNX improvements: Added Scene support, added support for exposing sensors to KNX bus, added reset value option for binary switches

* fixed import

* Bumped version of KNX library (minor upgrade with two important bugfixes)

* bumped version of xknx (now without python requirement *sigh*)

* Issue #11978: fixed review comments

* Issue #11978: hound suggestion fixed:

* review comments

* made async functions async

* Addressed issues mentined by @MartinHjelmare

* removed default=None from validation schema

* ATTR_ENTITY_ID->CONF_ENTITY_ID

* moved missing function to async syntax

* pylint

* Trigger notification

* Trigger notification

* fixed review comment
pull/12687/head
Julius Mittenzwei 2018-02-26 08:44:09 +01:00 committed by Paulus Schoutsen
parent 6d5fb49687
commit 7d5c1581f1
3 changed files with 179 additions and 2 deletions

View File

@ -25,6 +25,7 @@ CONF_DEFAULT_HOOK = 'on'
CONF_COUNTER = 'counter'
CONF_DEFAULT_COUNTER = 1
CONF_ACTION = 'action'
CONF_RESET_AFTER = 'reset_after'
CONF__ACTION = 'turn_off_action'
@ -48,6 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT):
cv.positive_int,
vol.Optional(CONF_RESET_AFTER): cv.positive_int,
vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
})
@ -81,7 +83,8 @@ def async_add_devices_config(hass, config, async_add_devices):
name=name,
group_address=config.get(CONF_ADDRESS),
device_class=config.get(CONF_DEVICE_CLASS),
significant_bit=config.get(CONF_SIGNIFICANT_BIT))
significant_bit=config.get(CONF_SIGNIFICANT_BIT),
reset_after=config.get(CONF_RESET_AFTER))
hass.data[DATA_KNX].xknx.devices.add(binary_sensor)
entity = KNXBinarySensor(hass, binary_sensor)

View File

@ -9,9 +9,12 @@ import logging
import voluptuous as vol
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.const import (
CONF_ENTITY_ID, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.script import Script
REQUIREMENTS = ['xknx==0.8.3']
@ -26,6 +29,9 @@ CONF_KNX_LOCAL_IP = "local_ip"
CONF_KNX_FIRE_EVENT = "fire_event"
CONF_KNX_FIRE_EVENT_FILTER = "fire_event_filter"
CONF_KNX_STATE_UPDATER = "state_updater"
CONF_KNX_EXPOSE = "expose"
CONF_KNX_EXPOSE_TYPE = "type"
CONF_KNX_EXPOSE_ADDRESS = "address"
SERVICE_KNX_SEND = "send"
SERVICE_KNX_ATTR_ADDRESS = "address"
@ -45,6 +51,12 @@ ROUTING_SCHEMA = vol.Schema({
vol.Required(CONF_KNX_LOCAL_IP): cv.string,
})
EXPOSE_SCHEMA = vol.Schema({
vol.Required(CONF_KNX_EXPOSE_TYPE): cv.string,
vol.Optional(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_KNX_EXPOSE_ADDRESS): cv.string,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_KNX_CONFIG): cv.string,
@ -56,6 +68,10 @@ CONFIG_SCHEMA = vol.Schema({
vol.Inclusive(CONF_KNX_FIRE_EVENT_FILTER, 'fire_ev'):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_KNX_STATE_UPDATER, default=True): cv.boolean,
vol.Optional(CONF_KNX_EXPOSE):
vol.All(
cv.ensure_list,
[EXPOSE_SCHEMA]),
})
}, extra=vol.ALLOW_EXTRA)
@ -71,6 +87,7 @@ async def async_setup(hass, config):
from xknx.exceptions import XKNXException
try:
hass.data[DATA_KNX] = KNXModule(hass, config)
hass.data[DATA_KNX].async_create_exposures()
await hass.data[DATA_KNX].start()
except XKNXException as ex:
@ -87,6 +104,7 @@ async def async_setup(hass, config):
('light', 'Light'),
('sensor', 'Sensor'),
('binary_sensor', 'BinarySensor'),
('scene', 'Scene'),
('notify', 'Notification')):
found_devices = _get_devices(hass, discovery_type)
hass.async_add_job(
@ -121,6 +139,7 @@ class KNXModule(object):
self.connected = False
self.init_xknx()
self.register_callbacks()
self.exposures = []
def init_xknx(self):
"""Initialize of KNX object."""
@ -199,6 +218,26 @@ class KNXModule(object):
self.xknx.telegram_queue.register_telegram_received_cb(
self.telegram_received_cb, address_filters)
@callback
def async_create_exposures(self):
"""Create exposures."""
if CONF_KNX_EXPOSE not in self.config[DOMAIN]:
return
for to_expose in self.config[DOMAIN][CONF_KNX_EXPOSE]:
expose_type = to_expose.get(CONF_KNX_EXPOSE_TYPE)
entity_id = to_expose.get(CONF_ENTITY_ID)
address = to_expose.get(CONF_KNX_EXPOSE_ADDRESS)
if expose_type in ['time', 'date', 'datetime']:
exposure = KNXExposeTime(
self.xknx, expose_type, address)
exposure.async_register()
self.exposures.append(exposure)
else:
exposure = KNXExposeSensor(
self.hass, self.xknx, expose_type, entity_id, address)
exposure.async_register()
self.exposures.append(exposure)
async def telegram_received_cb(self, telegram):
"""Call invoked after a KNX telegram was received."""
self.hass.bus.fire('knx_event', {
@ -243,3 +282,59 @@ class KNXAutomation():
hass.data[DATA_KNX].xknx, self.script.async_run,
hook=hook, counter=counter)
device.actions.append(self.action)
class KNXExposeTime(object):
"""Object to Expose Time/Date object to KNX bus."""
def __init__(self, xknx, expose_type, address):
"""Initialize of Expose class."""
self.xknx = xknx
self.type = expose_type
self.address = address
self.device = None
@callback
def async_register(self):
"""Register listener."""
from xknx.devices import DateTime, DateTimeBroadcastType
broadcast_type_string = self.type.upper()
broadcast_type = DateTimeBroadcastType[broadcast_type_string]
self.device = DateTime(
self.xknx,
'Time',
broadcast_type=broadcast_type,
group_address=self.address)
self.xknx.devices.add(self.device)
class KNXExposeSensor(object):
"""Object to Expose HASS entity to KNX bus."""
def __init__(self, hass, xknx, expose_type, entity_id, address):
"""Initialize of Expose class."""
self.hass = hass
self.xknx = xknx
self.type = expose_type
self.entity_id = entity_id
self.address = address
self.device = None
@callback
def async_register(self):
"""Register listener."""
from xknx.devices import ExposeSensor
self.device = ExposeSensor(
self.xknx,
name=self.entity_id,
group_address=self.address,
value_type=self.type)
self.xknx.devices.add(self.device)
async_track_state_change(
self.hass, self.entity_id, self._async_entity_changed)
async def _async_entity_changed(self, entity_id, old_state, new_state):
"""Callback after entity changed."""
if new_state is None:
return
await self.device.set(float(new_state.state))

View File

@ -0,0 +1,79 @@
"""
Support for KNX scenes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/scene.knx/
"""
import asyncio
import voluptuous as vol
from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.components.scene import CONF_PLATFORM, Scene
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
CONF_ADDRESS = 'address'
CONF_SCENE_NUMBER = 'scene_number'
DEFAULT_NAME = 'KNX SCENE'
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'knx',
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_ADDRESS): cv.string,
vol.Required(CONF_SCENE_NUMBER): cv.positive_int,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the scenes for KNX platform."""
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
else:
async_add_devices_config(hass, config, async_add_devices)
@callback
def async_add_devices_discovery(hass, discovery_info, async_add_devices):
"""Set up scenes for KNX platform configured via xknx.yaml."""
entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
entities.append(KNXScene(device))
async_add_devices(entities)
@callback
def async_add_devices_config(hass, config, async_add_devices):
"""Set up scene for KNX platform configured within platform."""
import xknx
scene = xknx.devices.Scene(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address=config.get(CONF_ADDRESS),
scene_number=config.get(CONF_SCENE_NUMBER))
hass.data[DATA_KNX].xknx.devices.add(scene)
async_add_devices([KNXScene(scene)])
class KNXScene(Scene):
"""Representation of a KNX scene."""
def __init__(self, scene):
"""Init KNX scene."""
self.scene = scene
@property
def name(self):
"""Return the name of the scene."""
return self.scene.name
@asyncio.coroutine
def async_activate(self):
"""Activate the scene."""
yield from self.scene.run()