Stable and asynchronous KNX library. (#8725)

* First draft of XKNX module for Home-Assistant

* XKNX does now take path of xknx.yaml as parameter

* small fix, telegram_received_callback has different signature

* changed method of registering callbacks of devices

* removed non async command lines from xknx

* telegram_received_cb not needed within HASS module

* updated requirements

* Configuration if XKNX should connect via Routing or Tunneling

* bumping version to 0.6.1

* small fix within xknx plugin

* bumped version

* XKNX-Switches are now BinarySensors and Logic from Sensor was moved to BinarySensor

* renamed Outlet to Switch

* pylint

* configuration of KNX lights via HASS config, yay!

* changed name of attribute

* Added configuration for xknx to switch component

* added support for sensors within hass configuration

* added support for climate within hass configuration

* Thermostat -> Climate

* added configuration support for binary_sensors

* renamed Shutter to Cover

* added configuration support for cover

* restructured file structure according to HASS requirements

* pylint

* pylint

* pylint

* pylint

* pylint

* pylint

* updated version

* pylint

* pylint

* pylint

* added setpoint support for climate devices

* devices are now in a different module

* more asyncio :-)

* pydocstyle

* pydocstyle

* added actions to binary_sensor

* allow more than one automation

* readded requirement

* Modifications suggested by hound

* Modifications suggested by hound

* Modifications suggested by hound

* Modifications suggested by hound

* xknx now imported as local import

* hound *sigh*

* lint

* 'fixed' coverage.

* next try for getting gen_requirements_all.py working

* removed blank line

* XKNX 0.7.1 with logging functionality, replaced some print() calls with _LOGGER

* updated requirements_all.txt

* Fixes issue https://github.com/XKNX/xknx/issues/51

* https://github.com/XKNX/xknx/issues/52 added raw access to KNX bus from HASS component.

* bumped version - 0.7.3 contains some bugfixes

* bumped version - 0.7.3 contains some bugfixes

* setting setpoint within climate device has to be async

* bumped version to 0.7.4

* bumped version

* https://github.com/XKNX/xknx/issues/48 Adding HVAC support.

* pylint suggestions

* Made target temperature and set point required attributes

* renamed value_type to type within sensor configuration

* Issue https://github.com/XKNX/xknx/issues/52 : added filter functionality for not flooding the event bus.

* suggestions by pylint

* Added notify support for knx platform.

* logging error if discovery_info is None.

* review suggestions by @armills

* line too long

* Using discovery_info to notifiy component which devices should be added.

* moved XKNX automation to main level.

* renamed xknx component to knx.

* reverted change within .coveragerc

* changed dependency

* updated docstrings.

* updated version of xknx within requirements_all.txt

* moved requirement to correct position

* renamed configuration attribute

* added @callback-decorator and async_prefix.

* added @callback decorator and async_ prefix to register_callbacks functions

* fixed typo

* pylint suggestions

* added angle position and invert_position and invert_angle to cover.knx

* typo

* bumped version within requirements_all.txt

* bumped version

* Added support for HVAC controller status
This commit is contained in:
Julius Mittenzwei 2017-09-07 09:11:55 +02:00 committed by Paulus Schoutsen
parent 9a7089bad3
commit 77d0ad1797
10 changed files with 1041 additions and 887 deletions

View file

@ -1,21 +1,145 @@
""" """
Contains functionality to use a KNX group address as a binary. Support for KNX/IP binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/ https://home-assistant.io/components/binary_sensor.knx/
""" """
from homeassistant.components.binary_sensor import BinarySensorDevice import asyncio
from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES, \
KNXAutomation
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, \
BinarySensorDevice
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
CONF_ADDRESS = 'address'
CONF_DEVICE_CLASS = 'device_class'
CONF_SIGNIFICANT_BIT = 'significant_bit'
CONF_DEFAULT_SIGNIFICANT_BIT = 1
CONF_AUTOMATION = 'automation'
CONF_HOOK = 'hook'
CONF_DEFAULT_HOOK = 'on'
CONF_COUNTER = 'counter'
CONF_DEFAULT_COUNTER = 1
CONF_ACTION = 'action'
CONF__ACTION = 'turn_off_action'
DEFAULT_NAME = 'KNX Binary Sensor'
DEPENDENCIES = ['knx'] DEPENDENCIES = ['knx']
AUTOMATION_SCHEMA = vol.Schema({
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
vol.Required(CONF_ACTION, default=None): cv.SCRIPT_SCHEMA
})
def setup_platform(hass, config, add_devices, discovery_info=None): AUTOMATIONS_SCHEMA = vol.All(
"""Set up the KNX binary sensor platform.""" cv.ensure_list,
add_devices([KNXSwitch(hass, KNXConfig(config))]) [AUTOMATION_SCHEMA]
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT):
cv.positive_int,
vol.Optional(CONF_AUTOMATION, default=None): AUTOMATIONS_SCHEMA,
})
class KNXSwitch(KNXGroupAddress, BinarySensorDevice): @asyncio.coroutine
"""Representation of a KNX binary sensor device.""" def async_setup_platform(hass, config, add_devices,
discovery_info=None):
"""Set up binary sensor(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
pass if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, add_devices)
else:
async_add_devices_config(hass, config, add_devices)
return True
@callback
def async_add_devices_discovery(hass, discovery_info, add_devices):
"""Set up binary sensors 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(KNXBinarySensor(hass, device))
add_devices(entities)
@callback
def async_add_devices_config(hass, config, add_devices):
"""Set up binary senor for KNX platform configured within plattform."""
name = config.get(CONF_NAME)
import xknx
binary_sensor = xknx.devices.BinarySensor(
hass.data[DATA_KNX].xknx,
name=name,
group_address=config.get(CONF_ADDRESS),
device_class=config.get(CONF_DEVICE_CLASS),
significant_bit=config.get(CONF_SIGNIFICANT_BIT))
hass.data[DATA_KNX].xknx.devices.add(binary_sensor)
entity = KNXBinarySensor(hass, binary_sensor)
automations = config.get(CONF_AUTOMATION)
if automations is not None:
for automation in automations:
counter = automation.get(CONF_COUNTER)
hook = automation.get(CONF_HOOK)
action = automation.get(CONF_ACTION)
entity.automations.append(KNXAutomation(
hass=hass, device=binary_sensor, hook=hook,
action=action, counter=counter))
add_devices([entity])
class KNXBinarySensor(BinarySensorDevice):
"""Representation of a KNX binary sensor."""
def __init__(self, hass, device):
"""Initialization of KNXBinarySensor."""
self.device = device
self.hass = hass
self.async_register_callbacks()
self.automations = []
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property
def device_class(self):
"""Return the class of this sensor."""
return self.device.device_class
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.device.is_on()

View file

@ -1,68 +1,136 @@
""" """
Support for KNX thermostats. Support for KNX/IP climate devices.
For more details about this platform, please refer to the documentation For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.knx/ https://home-assistant.io/components/climate.knx/
""" """
import logging import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA) from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice) from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.const import (CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE) from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_ADDRESS = 'address'
CONF_SETPOINT_ADDRESS = 'setpoint_address' CONF_SETPOINT_ADDRESS = 'setpoint_address'
CONF_TEMPERATURE_ADDRESS = 'temperature_address' CONF_TEMPERATURE_ADDRESS = 'temperature_address'
CONF_TARGET_TEMPERATURE_ADDRESS = 'target_temperature_address'
CONF_OPERATION_MODE_ADDRESS = 'operation_mode_address'
CONF_OPERATION_MODE_STATE_ADDRESS = 'operation_mode_state_address'
CONF_CONTROLLER_STATUS_ADDRESS = 'controller_status_address'
CONF_CONTROLLER_STATUS_STATE_ADDRESS = 'controller_status_state_address'
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = \
'operation_mode_frost_protection_address'
CONF_OPERATION_MODE_NIGHT_ADDRESS = 'operation_mode_night_address'
CONF_OPERATION_MODE_COMFORT_ADDRESS = 'operation_mode_comfort_address'
DEFAULT_NAME = 'KNX Thermostat' DEFAULT_NAME = 'KNX Climate'
DEPENDENCIES = ['knx'] DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_SETPOINT_ADDRESS): cv.string, vol.Required(CONF_SETPOINT_ADDRESS): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string, vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine
"""Create and add an entity based on the configuration.""" def async_setup_platform(hass, config, add_devices,
add_devices([KNXThermostat(hass, KNXConfig(config))]) discovery_info=None):
"""Set up climate(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, add_devices)
else:
async_add_devices_config(hass, config, add_devices)
return True
class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): @callback
"""Representation of a KNX thermostat. def async_add_devices_discovery(hass, discovery_info, add_devices):
"""Set up climates for KNX platform configured within plattform."""
entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
entities.append(KNXClimate(hass, device))
add_devices(entities)
A KNX thermostat will has the following parameters:
- temperature (current temperature)
- setpoint (target temperature in HASS terms)
- operation mode selection (comfort/night/frost protection)
This version supports only polling. Messages from the KNX bus do not @callback
automatically update the state of the thermostat (to be implemented def async_add_devices_config(hass, config, add_devices):
in future releases) """Set up climate for KNX platform configured within plattform."""
""" import xknx
climate = xknx.devices.Climate(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address_temperature=config.get(
CONF_TEMPERATURE_ADDRESS),
group_address_target_temperature=config.get(
CONF_TARGET_TEMPERATURE_ADDRESS),
group_address_setpoint=config.get(
CONF_SETPOINT_ADDRESS),
group_address_operation_mode=config.get(
CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get(
CONF_CONTROLLER_STATUS_ADDRESS),
group_address_controller_status_state=config.get(
CONF_CONTROLLER_STATUS_STATE_ADDRESS),
group_address_operation_mode_protection=config.get(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS),
group_address_operation_mode_night=config.get(
CONF_OPERATION_MODE_NIGHT_ADDRESS),
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS))
hass.data[DATA_KNX].xknx.devices.add(climate)
add_devices([KNXClimate(hass, climate)])
def __init__(self, hass, config):
"""Initialize the thermostat based on the given configuration."""
KNXMultiAddressDevice.__init__(
self, hass, config, ['temperature', 'setpoint'], ['mode'])
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius class KNXClimate(ClimateDevice):
"""Representation of a KNX climate."""
def __init__(self, hass, device):
"""Initialization of KNXClimate."""
self.device = device
self.hass = hass
self.async_register_callbacks()
self._unit_of_measurement = TEMP_CELSIUS
self._away = False # not yet supported self._away = False # not yet supported
self._is_fan_on = False # not yet supported self._is_fan_on = False # not yet supported
self._current_temp = None
self._target_temp = None def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property @property
def should_poll(self): def should_poll(self):
"""Return the polling state, is needed for the KNX thermostat.""" """No polling needed within KNX."""
return True return False
@property @property
def temperature_unit(self): def temperature_unit(self):
@ -72,32 +140,42 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
@property @property
def current_temperature(self): def current_temperature(self):
"""Return the current temperature.""" """Return the current temperature."""
return self._current_temp return self.device.temperature
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self._target_temp if self.device.supports_target_temperature:
return self.device.target_temperature
return None
def set_temperature(self, **kwargs): @asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE) temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None: if temperature is None:
return return
from knxip.conversion import float_to_knx2 if self.device.supports_target_temperature:
yield from self.device.set_target_temperature(temperature)
self.set_value('setpoint', float_to_knx2(temperature)) @property
_LOGGER.debug("Set target temperature to %s", temperature) def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.supports_operation_mode:
return self.device.operation_mode.value
return None
def set_operation_mode(self, operation_mode): @property
def operation_list(self):
"""Return the list of available operation modes."""
return [operation_mode.value for
operation_mode in
self.device.get_supported_operation_modes()]
@asyncio.coroutine
def async_set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
raise NotImplementedError() if self.device.supports_operation_mode:
from xknx.knx import HVACOperationMode
def update(self): knx_operation_mode = HVACOperationMode(operation_mode)
"""Update KNX climate.""" yield from self.device.set_operation_mode(knx_operation_mode)
from knxip.conversion import knx2_to_float
super().update()
self._current_temp = knx2_to_float(self.value('temperature'))
self._target_temp = knx2_to_float(self.value('setpoint'))

View file

@ -1,185 +1,239 @@
""" """
Support for KNX covers. Support for KNX/IP covers.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.knx/ https://home-assistant.io/components/cover.knx/
""" """
import logging import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.helpers.event import async_track_utc_time_change
from homeassistant.components.cover import ( from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA, ATTR_POSITION, DEVICE_CLASSES_SCHEMA, CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE,
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, SUPPORT_STOP, SUPPORT_SET_POSITION, SUPPORT_STOP, SUPPORT_SET_TILT_POSITION,
SUPPORT_SET_TILT_POSITION ATTR_POSITION, ATTR_TILT_POSITION)
) from homeassistant.core import callback
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice) from homeassistant.const import CONF_NAME
from homeassistant.const import (CONF_NAME, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) CONF_MOVE_LONG_ADDRESS = 'move_long_address'
CONF_MOVE_SHORT_ADDRESS = 'move_short_address'
CONF_GETPOSITION_ADDRESS = 'getposition_address' CONF_POSITION_ADDRESS = 'position_address'
CONF_SETPOSITION_ADDRESS = 'setposition_address' CONF_POSITION_STATE_ADDRESS = 'position_state_address'
CONF_GETANGLE_ADDRESS = 'getangle_address' CONF_ANGLE_ADDRESS = 'angle_address'
CONF_SETANGLE_ADDRESS = 'setangle_address' CONF_ANGLE_STATE_ADDRESS = 'angle_state_address'
CONF_STOP = 'stop_address' CONF_TRAVELLING_TIME_DOWN = 'travelling_time_down'
CONF_UPDOWN = 'updown_address' CONF_TRAVELLING_TIME_UP = 'travelling_time_up'
CONF_INVERT_POSITION = 'invert_position' CONF_INVERT_POSITION = 'invert_position'
CONF_INVERT_ANGLE = 'invert_angle' CONF_INVERT_ANGLE = 'invert_angle'
DEFAULT_TRAVEL_TIME = 25
DEFAULT_NAME = 'KNX Cover' DEFAULT_NAME = 'KNX Cover'
DEPENDENCIES = ['knx'] DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_UPDOWN): cv.string,
vol.Required(CONF_STOP): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_GETPOSITION_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SETPOSITION_ADDRESS): cv.string, vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string,
vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string,
vol.Optional(CONF_POSITION_ADDRESS): cv.string,
vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string,
vol.Optional(CONF_ANGLE_ADDRESS): cv.string,
vol.Optional(CONF_ANGLE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME):
cv.positive_int,
vol.Optional(CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME):
cv.positive_int,
vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
vol.Inclusive(CONF_GETANGLE_ADDRESS, 'angle'): cv.string,
vol.Inclusive(CONF_SETANGLE_ADDRESS, 'angle'): cv.string,
vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean,
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine
"""Create and add an entity based on the configuration.""" def async_setup_platform(hass, config, add_devices,
add_devices([KNXCover(hass, KNXConfig(config))]) discovery_info=None):
"""Set up cover(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, add_devices)
else:
async_add_devices_config(hass, config, add_devices)
return True
class KNXCover(KNXMultiAddressDevice, CoverDevice): @callback
"""Representation of a KNX cover. e.g. a rollershutter.""" def async_add_devices_discovery(hass, discovery_info, add_devices):
"""Set up covers 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(KNXCover(hass, device))
add_devices(entities)
def __init__(self, hass, config):
@callback
def async_add_devices_config(hass, config, add_devices):
"""Set up cover for KNX platform configured within plattform."""
import xknx
cover = xknx.devices.Cover(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address_long=config.get(CONF_MOVE_LONG_ADDRESS),
group_address_short=config.get(CONF_MOVE_SHORT_ADDRESS),
group_address_position_state=config.get(
CONF_POSITION_STATE_ADDRESS),
group_address_angle=config.get(CONF_ANGLE_ADDRESS),
group_address_angle_state=config.get(CONF_ANGLE_STATE_ADDRESS),
group_address_position=config.get(CONF_POSITION_ADDRESS),
travel_time_down=config.get(CONF_TRAVELLING_TIME_DOWN),
travel_time_up=config.get(CONF_TRAVELLING_TIME_UP))
invert_position = config.get(CONF_INVERT_POSITION)
invert_angle = config.get(CONF_INVERT_ANGLE)
hass.data[DATA_KNX].xknx.devices.add(cover)
add_devices([KNXCover(hass, cover, invert_position, invert_angle)])
class KNXCover(CoverDevice):
"""Representation of a KNX cover."""
def __init__(self, hass, device, invert_position=False,
invert_angle=False):
"""Initialize the cover.""" """Initialize the cover."""
KNXMultiAddressDevice.__init__( self.device = device
self, hass, config, self.invert_position = invert_position
['updown', 'stop'], # required self.invert_angle = invert_angle
optional=['setposition', 'getposition', self.hass = hass
'getangle', 'setangle'] self.async_register_callbacks()
)
self._device_class = config.config.get(CONF_DEVICE_CLASS)
self._invert_position = config.config.get(CONF_INVERT_POSITION)
self._invert_angle = config.config.get(CONF_INVERT_ANGLE)
self._hass = hass
self._current_pos = None
self._target_pos = None
self._current_tilt = None
self._target_tilt = None
self._supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | \
SUPPORT_SET_POSITION | SUPPORT_STOP
# Tilt is only supported, if there is a angle get and set address self._unsubscribe_auto_updater = None
if CONF_SETANGLE_ADDRESS in config.config:
_LOGGER.debug("%s: Tilt supported at addresses %s, %s", @callback
self.name, config.config.get(CONF_SETANGLE_ADDRESS), def async_register_callbacks(self):
config.config.get(CONF_GETANGLE_ADDRESS)) """Register callbacks to update hass after device was changed."""
self._supported_features = self._supported_features | \ @asyncio.coroutine
SUPPORT_SET_TILT_POSITION def after_update_callback(device):
"""Callback after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property @property
def should_poll(self): def should_poll(self):
"""Polling is needed for the KNX cover.""" """No polling needed within KNX."""
return True return False
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
return self._supported_features supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | \
SUPPORT_SET_POSITION | SUPPORT_STOP
if self.device.supports_angle:
supported_features |= SUPPORT_SET_TILT_POSITION
return supported_features
@property
def current_cover_position(self):
"""Return the current position of the cover."""
return int(self.from_knx_position(
self.device.current_position(),
self.invert_position))
@property @property
def is_closed(self): def is_closed(self):
"""Return if the cover is closed.""" """Return if the cover is closed."""
if self.current_cover_position is not None: return self.device.is_closed()
if self.current_cover_position > 0:
return False
else:
return True
@property @asyncio.coroutine
def current_cover_position(self): def async_close_cover(self, **kwargs):
"""Return current position of cover. """Close the cover."""
if not self.device.is_closed():
yield from self.device.set_down()
self.start_auto_updater()
None is unknown, 0 is closed, 100 is fully open. @asyncio.coroutine
""" def async_open_cover(self, **kwargs):
return self._current_pos """Open the cover."""
if not self.device.is_open():
yield from self.device.set_up()
self.start_auto_updater()
@property @asyncio.coroutine
def target_position(self): def async_set_cover_position(self, **kwargs):
"""Return the position we are trying to reach: 0 - 100.""" """Move the cover to a specific position."""
return self._target_pos if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
knx_position = self.to_knx_position(position, self.invert_position)
yield from self.device.set_position(knx_position)
self.start_auto_updater()
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
"""Stop the cover."""
yield from self.device.stop()
self.stop_auto_updater()
@property @property
def current_cover_tilt_position(self): def current_cover_tilt_position(self):
"""Return current position of cover. """Return current tilt position of cover."""
if not self.device.supports_angle:
return None
return int(self.from_knx_position(
self.device.angle,
self.invert_angle))
None is unknown, 0 is closed, 100 is fully open. @asyncio.coroutine
""" def async_set_cover_tilt_position(self, **kwargs):
return self._current_tilt """Move the cover tilt to a specific position."""
if ATTR_TILT_POSITION in kwargs:
position = kwargs[ATTR_TILT_POSITION]
knx_position = self.to_knx_position(position, self.invert_angle)
yield from self.device.set_angle(knx_position)
@property def start_auto_updater(self):
def target_tilt(self): """Start the autoupdater to update HASS while cover is moving."""
"""Return the tilt angle (in %) we are trying to reach: 0 - 100.""" if self._unsubscribe_auto_updater is None:
return self._target_tilt self._unsubscribe_auto_updater = async_track_utc_time_change(
self.hass, self.auto_updater_hook)
def set_cover_position(self, **kwargs): def stop_auto_updater(self):
"""Set new target position.""" """Stop the autoupdater."""
position = kwargs.get(ATTR_POSITION) if self._unsubscribe_auto_updater is not None:
if position is None: self._unsubscribe_auto_updater()
return self._unsubscribe_auto_updater = None
if self._invert_position: @callback
position = 100-position def auto_updater_hook(self, now):
"""Callback for autoupdater."""
# pylint: disable=unused-argument
self.hass.async_add_job(self.async_update_ha_state())
if self.device.position_reached():
self.stop_auto_updater()
self._target_pos = position self.hass.add_job(self.device.auto_stop_if_necessary())
self.set_percentage('setposition', position)
_LOGGER.debug("%s: Set target position to %d", self.name, position)
def update(self): @staticmethod
"""Update device state.""" def from_knx_position(raw, invert):
super().update() """Convert KNX position [0...255] to hass position [100...0]."""
value = self.get_percentage('getposition') position = round((raw/256)*100)
if value is not None: if not invert:
self._current_pos = value position = 100 - position
if self._invert_position: return position
self._current_pos = 100-value
_LOGGER.debug("%s: position = %d", self.name, value)
if self._supported_features & SUPPORT_SET_TILT_POSITION: @staticmethod
value = self.get_percentage('getangle') def to_knx_position(value, invert):
if value is not None: """Convert hass position [100...0] to KNX position [0...255]."""
self._current_tilt = value knx_position = round(value/100*255.4)
if self._invert_angle: if not invert:
self._current_tilt = 100-value knx_position = 255-knx_position
_LOGGER.debug("%s: tilt = %d", self.name, value) print(value, " -> ", knx_position)
return knx_position
def open_cover(self, **kwargs):
"""Open the cover."""
_LOGGER.debug("%s: open: updown = 0", self.name)
self.set_int_value('updown', 0)
def close_cover(self, **kwargs):
"""Close the cover."""
_LOGGER.debug("%s: open: updown = 1", self.name)
self.set_int_value('updown', 1)
def stop_cover(self, **kwargs):
"""Stop the cover movement."""
_LOGGER.debug("%s: stop: stop = 1", self.name)
self.set_int_value('stop', 1)
def set_cover_tilt_position(self, tilt_position, **kwargs):
"""Move the cover til to a specific position."""
if self._invert_angle:
tilt_position = 100-tilt_position
self._target_tilt = round(tilt_position, -1)
self.set_percentage('setangle', tilt_position)
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class

View file

@ -1,495 +1,255 @@
""" """
Support for KNX components.
For more details about this component, please refer to the documentation at Connects to KNX platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/knx/ https://home-assistant.io/components/knx/
""" """
import logging import logging
import os import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import EVENT_HOMEASSISTANT_STOP, \
EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT) CONF_HOST, CONF_PORT
from homeassistant.helpers.entity import Entity from homeassistant.helpers.script import Script
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['knxip==0.5'] DOMAIN = "knx"
DATA_KNX = "data_knx"
CONF_KNX_CONFIG = "config_file"
CONF_KNX_ROUTING = "routing"
CONF_KNX_TUNNELING = "tunneling"
CONF_KNX_LOCAL_IP = "local_ip"
CONF_KNX_FIRE_EVENT = "fire_event"
CONF_KNX_FIRE_EVENT_FILTER = "fire_event_filter"
SERVICE_KNX_SEND = "send"
SERVICE_KNX_ATTR_ADDRESS = "address"
SERVICE_KNX_ATTR_PAYLOAD = "payload"
ATTR_DISCOVER_DEVICES = 'devices'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = '0.0.0.0' REQUIREMENTS = ['xknx==0.7.13']
DEFAULT_PORT = 3671
DOMAIN = 'knx'
EVENT_KNX_FRAME_RECEIVED = 'knx_frame_received' TUNNELING_SCHEMA = vol.Schema({
EVENT_KNX_FRAME_SEND = 'knx_frame_send' vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Required(CONF_KNX_LOCAL_IP): cv.string,
})
KNXTUNNEL = None ROUTING_SCHEMA = vol.Schema({
KNX_ADDRESS = "address" vol.Required(CONF_KNX_LOCAL_IP): cv.string,
KNX_DATA = "data" })
KNX_GROUP_WRITE = "group_write"
CONF_LISTEN = "listen"
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_KNX_CONFIG): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Exclusive(CONF_KNX_ROUTING, 'connection_type'): ROUTING_SCHEMA,
vol.Optional(CONF_LISTEN, default=[]): vol.Exclusive(CONF_KNX_TUNNELING, 'connection_type'):
vol.All(cv.ensure_list, [cv.string]), TUNNELING_SCHEMA,
}), vol.Inclusive(CONF_KNX_FIRE_EVENT, 'fire_ev'):
cv.boolean,
vol.Inclusive(CONF_KNX_FIRE_EVENT_FILTER, 'fire_ev'):
vol.All(
cv.ensure_list,
[cv.string])
})
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
KNX_WRITE_SCHEMA = vol.Schema({ SERVICE_KNX_SEND_SCHEMA = vol.Schema({
vol.Required(KNX_ADDRESS): vol.All(cv.ensure_list, [cv.string]), vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string,
vol.Required(KNX_DATA): vol.All(cv.ensure_list, [cv.byte]) vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any(
cv.positive_int, [cv.positive_int]),
}) })
def setup(hass, config): @asyncio.coroutine
"""Set up the connection to the KNX IP interface.""" def async_setup(hass, config):
global KNXTUNNEL """Set up knx component."""
from xknx.exceptions import XKNXException
from knxip.ip import KNXIPTunnel
from knxip.core import KNXException, parse_group_address
host = config[DOMAIN].get(CONF_HOST)
port = config[DOMAIN].get(CONF_PORT)
if host == '0.0.0.0':
_LOGGER.debug("Will try to auto-detect KNX/IP gateway")
KNXTUNNEL = KNXIPTunnel(host, port)
try: try:
res = KNXTUNNEL.connect() hass.data[DATA_KNX] = KNXModule(hass, config)
_LOGGER.debug("Res = %s", res) yield from hass.data[DATA_KNX].start()
if not res:
_LOGGER.error("Could not connect to KNX/IP interface %s", host)
return False
except KNXException as ex: except XKNXException as ex:
_LOGGER.exception("Can't connect to KNX/IP interface: %s", ex) _LOGGER.exception("Can't connect to KNX interface: %s", ex)
KNXTUNNEL = None
return False return False
_LOGGER.info("KNX IP tunnel to %s:%i established", host, port) for component, discovery_type in (
('switch', 'Switch'),
('climate', 'Climate'),
('cover', 'Cover'),
('light', 'Light'),
('sensor', 'Sensor'),
('binary_sensor', 'BinarySensor'),
('notify', 'Notification')):
found_devices = _get_devices(hass, discovery_type)
hass.async_add_job(
discovery.async_load_platform(hass, component, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
}, config))
descriptions = load_yaml_config_file( hass.services.async_register(
os.path.join(os.path.dirname(__file__), 'services.yaml')) DOMAIN, SERVICE_KNX_SEND,
hass.data[DATA_KNX].service_send_to_knx_bus,
def received_knx_event(address, data): schema=SERVICE_KNX_SEND_SCHEMA)
"""Process received KNX message."""
if len(data) == 1:
data = data[0]
hass.bus.fire('knx_event', {
'address': address,
'data': data
})
for listen in config[DOMAIN].get(CONF_LISTEN):
_LOGGER.debug("Registering listener for %s", listen)
try:
KNXTUNNEL.register_listener(parse_group_address(listen),
received_knx_event)
except KNXException as knxexception:
_LOGGER.error("Can't register KNX listener for address %s (%s)",
listen, knxexception)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_tunnel)
# Listen to KNX events and send them to the bus
def handle_group_write(call):
"""Bridge knx_frame_send events to the KNX bus."""
# parameters are pre-validated using KNX_WRITE_SCHEMA
addrlist = call.data.get("address")
knxdata = call.data.get("data")
knxaddrlist = []
for addr in addrlist:
try:
_LOGGER.debug("Found %s", addr)
knxaddr = int(addr)
except ValueError:
knxaddr = None
if knxaddr is None:
try:
knxaddr = parse_group_address(addr)
except KNXException:
_LOGGER.error("KNX address format incorrect: %s", addr)
knxaddrlist.append(knxaddr)
for addr in knxaddrlist:
KNXTUNNEL.group_write(addr, knxdata)
# Listen for when knx_frame_send event is fired
hass.services.register(DOMAIN,
KNX_GROUP_WRITE,
handle_group_write,
descriptions[DOMAIN][KNX_GROUP_WRITE],
schema=KNX_WRITE_SCHEMA)
return True return True
def close_tunnel(_data): def _get_devices(hass, discovery_type):
"""Close the NKX tunnel connection on shutdown.""" return list(
global KNXTUNNEL map(lambda device: device.name,
filter(
KNXTUNNEL.disconnect() lambda device: type(device).__name__ == discovery_type,
KNXTUNNEL = None hass.data[DATA_KNX].xknx.devices)))
class KNXConfig(object): class KNXModule(object):
"""Handle the fetching of configuration from the config file.""" """Representation of KNX Object."""
def __init__(self, config):
"""Initialize the configuration."""
from knxip.core import parse_group_address
self.config = config
self.should_poll = config.get('poll', True)
if config.get('address'):
self._address = parse_group_address(config.get('address'))
else:
self._address = None
if self.config.get('state_address'):
self._state_address = parse_group_address(
self.config.get('state_address'))
else:
self._state_address = None
@property
def name(self):
"""Return the name given to the entity."""
return self.config['name']
@property
def address(self):
"""Return the address of the device as an integer value.
3 types of addresses are supported:
integer - 0-65535
2 level - a/b
3 level - a/b/c
"""
return self._address
@property
def state_address(self):
"""Return the group address the device sends its current state to.
Some KNX devices can send the current state to a seperate
group address. This makes send e.g. when an actuator can
be switched but also have a timer functionality.
"""
return self._state_address
class KNXGroupAddress(Entity):
"""Representation of devices connected to a KNX group address."""
def __init__(self, hass, config): def __init__(self, hass, config):
"""Initialize the device.""" """Initialization of KNXModule."""
self._config = config self.hass = hass
self._state = False self.config = config
self._data = None self.initialized = False
_LOGGER.debug( self.init_xknx()
"Initalizing KNX group address for %s (%s)", self.register_callbacks()
self.name, self.address
)
def handle_knx_message(addr, data): def init_xknx(self):
"""Handle an incoming KNX frame. """Initialization of KNX object."""
from xknx import XKNX
self.xknx = XKNX(
config=self.config_file(),
loop=self.hass.loop)
Handle an incoming frame and update our status if it contains @asyncio.coroutine
information relating to this device. def start(self):
""" """Start KNX object. Connect to tunneling or Routing device."""
if (addr == self.state_address) or (addr == self.address): connection_config = self.connection_config()
self._state = data[0] yield from self.xknx.start(
self.schedule_update_ha_state() state_updater=True,
connection_config=connection_config)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
self.initialized = True
KNXTUNNEL.register_listener(self.address, handle_knx_message) @asyncio.coroutine
if self.state_address: def stop(self, event):
KNXTUNNEL.register_listener(self.state_address, handle_knx_message) """Stop KNX object. Disconnect from tunneling or Routing device."""
yield from self.xknx.stop()
@property def config_file(self):
def name(self): """Resolve and return the full path of xknx.yaml if configured."""
"""Return the entity's display name.""" config_file = self.config[DOMAIN].get(CONF_KNX_CONFIG)
return self._config.name if not config_file:
return None
if not config_file.startswith("/"):
return self.hass.config.path(config_file)
return config_file
@property def connection_config(self):
def config(self): """Return the connection_config."""
"""Return the entity's configuration.""" if CONF_KNX_TUNNELING in self.config[DOMAIN]:
return self._config return self.connection_config_tunneling()
elif CONF_KNX_ROUTING in self.config[DOMAIN]:
return self.connection_config_routing()
return self.connection_config_auto()
@property def connection_config_routing(self):
def should_poll(self): """Return the connection_config if routing is configured."""
"""Return the state of the polling, if needed.""" from xknx.io import ConnectionConfig, ConnectionType
return self._config.should_poll local_ip = \
self.config[DOMAIN][CONF_KNX_ROUTING].get(CONF_KNX_LOCAL_IP)
return ConnectionConfig(
connection_type=ConnectionType.ROUTING,
local_ip=local_ip)
@property def connection_config_tunneling(self):
def is_on(self): """Return the connection_config if tunneling is configured."""
"""Return True if the value is not 0 is on, else False.""" from xknx.io import ConnectionConfig, ConnectionType, \
return self._state != 0 DEFAULT_MCAST_PORT
gateway_ip = \
self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_HOST)
gateway_port = \
self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_PORT)
local_ip = \
self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_KNX_LOCAL_IP)
if gateway_port is None:
gateway_port = DEFAULT_MCAST_PORT
return ConnectionConfig(
connection_type=ConnectionType.TUNNELING,
gateway_ip=gateway_ip,
gateway_port=gateway_port,
local_ip=local_ip)
@property def connection_config_auto(self):
def address(self): """Return the connection_config if auto is configured."""
"""Return the KNX group address.""" # pylint: disable=no-self-use
return self._config.address from xknx.io import ConnectionConfig
return ConnectionConfig()
@property def register_callbacks(self):
def state_address(self): """Register callbacks within XKNX object."""
"""Return the KNX group address.""" if CONF_KNX_FIRE_EVENT in self.config[DOMAIN] and \
return self._config.state_address self.config[DOMAIN][CONF_KNX_FIRE_EVENT]:
from xknx.knx import AddressFilter
address_filters = list(map(
AddressFilter,
self.config[DOMAIN][CONF_KNX_FIRE_EVENT_FILTER]))
self.xknx.telegram_queue.register_telegram_received_cb(
self.telegram_received_cb, address_filters)
@property @asyncio.coroutine
def cache(self): def telegram_received_cb(self, telegram):
"""Return the name given to the entity.""" """Callback invoked after a KNX telegram was received."""
return self._config.config.get('cache', True) self.hass.bus.fire('knx_event', {
'address': telegram.group_address.str(),
def group_write(self, value): 'data': telegram.payload.value
"""Write to the group address.""" })
KNXTUNNEL.group_write(self.address, [value]) # False signals XKNX to proceed with processing telegrams.
def update(self):
"""Get the state from KNX bus or cache."""
from knxip.core import KNXException
try:
if self.state_address:
res = KNXTUNNEL.group_read(
self.state_address, use_cache=self.cache)
else:
res = KNXTUNNEL.group_read(self.address, use_cache=self.cache)
if res:
self._state = res[0]
self._data = res
else:
_LOGGER.debug(
"%s: unable to read from KNX address: %s (None)",
self.name, self.address
)
except KNXException:
_LOGGER.exception(
"%s: unable to read from KNX address: %s",
self.name, self.address
)
return False
class KNXMultiAddressDevice(Entity):
"""Representation of devices connected to a multiple KNX group address.
This is needed for devices like dimmers or shutter actuators as they have
to be controlled by multiple group addresses.
"""
def __init__(self, hass, config, required, optional=None):
"""Initialize the device.
The namelist argument lists the required addresses. E.g. for a dimming
actuators, the namelist might look like:
onoff_address: 0/0/1
brightness_address: 0/0/2
"""
from knxip.core import parse_group_address, KNXException
self.names = {}
self.values = {}
self._config = config
self._state = False
self._data = None
_LOGGER.debug(
"%s: initalizing KNX multi address device",
self.name
)
settings = self._config.config
if config.address:
_LOGGER.debug(
"%s: base address: address=%s",
self.name, settings.get('address')
)
self.names[config.address] = 'base'
if config.state_address:
_LOGGER.debug(
"%s, state address: state_address=%s",
self.name, settings.get('state_address')
)
self.names[config.state_address] = 'state'
# parse required addresses
for name in required:
paramname = '{}{}'.format(name, '_address')
addr = settings.get(paramname)
if addr is None:
_LOGGER.error(
"%s: Required KNX group address %s missing",
self.name, paramname
)
raise KNXException(
"%s: Group address for {} missing in "
"configuration for {}".format(
self.name, paramname
)
)
_LOGGER.debug(
"%s: (required parameter) %s=%s",
self.name, paramname, addr
)
addr = parse_group_address(addr)
self.names[addr] = name
# parse optional addresses
for name in optional:
paramname = '{}{}'.format(name, '_address')
addr = settings.get(paramname)
_LOGGER.debug(
"%s: (optional parameter) %s=%s",
self.name, paramname, addr
)
if addr:
try:
addr = parse_group_address(addr)
except KNXException:
_LOGGER.exception(
"%s: cannot parse group address %s",
self.name, addr
)
self.names[addr] = name
@property
def name(self):
"""Return the entity's display name."""
return self._config.name
@property
def config(self):
"""Return the entity's configuration."""
return self._config
@property
def should_poll(self):
"""Return the state of the polling, if needed."""
return self._config.should_poll
@property
def cache(self):
"""Return the name given to the entity."""
return self._config.config.get('cache', True)
def has_attribute(self, name):
"""Check if the attribute with the given name is defined.
This is mostly important for optional addresses.
"""
for attributename in self.names.values():
if attributename == name:
return True
return False return False
def set_percentage(self, name, percentage): @asyncio.coroutine
"""Set a percentage in knx for a given attribute. def service_send_to_knx_bus(self, call):
"""Service for sending an arbitray KNX message to the KNX bus."""
from xknx.knx import Telegram, Address, DPTBinary, DPTArray
attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD)
attr_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS)
DPT_Scaling / DPT 5.001 is a single byte scaled percentage def calculate_payload(attr_payload):
""" """Calculate payload depending on type of attribute."""
percentage = abs(percentage) # only accept positive values if isinstance(attr_payload, int):
scaled_value = percentage * 255 / 100 return DPTBinary(attr_payload)
value = min(255, scaled_value) return DPTArray(attr_payload)
return self.set_int_value(name, value) payload = calculate_payload(attr_payload)
address = Address(attr_address)
def get_percentage(self, name): telegram = Telegram()
"""Get a percentage from knx for a given attribute. telegram.payload = payload
telegram.group_address = address
yield from self.xknx.telegrams.put(telegram)
DPT_Scaling / DPT 5.001 is a single byte scaled percentage
"""
value = self.get_int_value(name)
percentage = round(value * 100 / 255)
return percentage
def set_int_value(self, name, value, num_bytes=1): class KNXAutomation():
"""Set an integer value for a given attribute.""" """Wrapper around xknx.devices.ActionCallback object.."""
# KNX packets are big endian
value = round(value) # only accept integers
b_value = value.to_bytes(num_bytes, byteorder='big')
return self.set_value(name, list(b_value))
def get_int_value(self, name): def __init__(self, hass, device, hook, action, counter=1):
"""Get an integer value for a given attribute.""" """Initialize Automation class."""
# KNX packets are big endian self.hass = hass
summed_value = 0 self.device = device
raw_value = self.value(name) script_name = "{} turn ON script".format(device.get_name())
try: self.script = Script(hass, action, script_name)
# convert raw value in bytes
for val in raw_value:
summed_value *= 256
summed_value += val
except TypeError:
# pknx returns a non-iterable type for unsuccessful reads
pass
return summed_value import xknx
self.action = xknx.devices.ActionCallback(
def value(self, name): hass.data[DATA_KNX].xknx,
"""Return the value to a given named attribute.""" self.script.async_run,
from knxip.core import KNXException hook=hook,
counter=counter)
addr = None device.actions.append(self.action)
for attributeaddress, attributename in self.names.items():
if attributename == name:
addr = attributeaddress
if addr is None:
_LOGGER.error("%s: attribute '%s' undefined",
self.name, name)
_LOGGER.debug(
"%s: defined attributes: %s",
self.name, str(self.names)
)
return False
try:
res = KNXTUNNEL.group_read(addr, use_cache=self.cache)
except KNXException:
_LOGGER.exception(
"%s: unable to read from KNX address: %s",
self.name, addr
)
return False
return res
def set_value(self, name, value):
"""Set the value of a given named attribute."""
from knxip.core import KNXException
addr = None
for attributeaddress, attributename in self.names.items():
if attributename == name:
addr = attributeaddress
if addr is None:
_LOGGER.error("%s: attribute '%s' undefined",
self.name, name)
_LOGGER.debug(
"%s: defined attributes: %s",
self.name, str(self.names)
)
return False
try:
KNXTUNNEL.group_write(addr, value)
except KNXException:
_LOGGER.exception(
"%s: unable to write to KNX address: %s",
self.name, addr
)
return False
return True

View file

@ -1,17 +1,17 @@
""" """
Support KNX Lighting actuators. Support for KNX/IP lights.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/Light.knx/ https://home-assistant.io/components/light.knx/
""" """
import logging import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice) from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.light import (Light, PLATFORM_SCHEMA, from homeassistant.components.light import PLATFORM_SCHEMA, Light, \
SUPPORT_BRIGHTNESS, SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS
ATTR_BRIGHTNESS)
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
CONF_ADDRESS = 'address' CONF_ADDRESS = 'address'
@ -19,8 +19,6 @@ CONF_STATE_ADDRESS = 'state_address'
CONF_BRIGHTNESS_ADDRESS = 'brightness_address' CONF_BRIGHTNESS_ADDRESS = 'brightness_address'
CONF_BRIGHTNESS_STATE_ADDRESS = 'brightness_state_address' CONF_BRIGHTNESS_STATE_ADDRESS = 'brightness_state_address'
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'KNX Light' DEFAULT_NAME = 'KNX Light'
DEPENDENCIES = ['knx'] DEPENDENCIES = ['knx']
@ -33,84 +31,136 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine
"""Set up the KNX light platform.""" def async_setup_platform(hass, config, add_devices,
add_devices([KNXLight(hass, KNXConfig(config))]) discovery_info=None):
"""Set up light(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, add_devices)
else:
async_add_devices_config(hass, config, add_devices)
return True
class KNXLight(KNXMultiAddressDevice, Light): @callback
"""Representation of a KNX Light device.""" def async_add_devices_discovery(hass, discovery_info, add_devices):
"""Set up lights 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(KNXLight(hass, device))
add_devices(entities)
def __init__(self, hass, config):
"""Initialize the cover."""
KNXMultiAddressDevice.__init__(
self, hass, config,
[], # required
optional=['state', 'brightness', 'brightness_state']
)
self._hass = hass
self._supported_features = 0
if CONF_BRIGHTNESS_ADDRESS in config.config: @callback
_LOGGER.debug("%s is dimmable", self.name) def async_add_devices_config(hass, config, add_devices):
self._supported_features = self._supported_features | \ """Set up light for KNX platform configured within plattform."""
SUPPORT_BRIGHTNESS import xknx
self._brightness = None light = xknx.devices.Light(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address_switch=config.get(CONF_ADDRESS),
group_address_switch_state=config.get(CONF_STATE_ADDRESS),
group_address_brightness=config.get(CONF_BRIGHTNESS_ADDRESS),
group_address_brightness_state=config.get(
CONF_BRIGHTNESS_STATE_ADDRESS))
hass.data[DATA_KNX].xknx.devices.add(light)
add_devices([KNXLight(hass, light)])
def turn_on(self, **kwargs):
"""Turn the switch on.
This sends a value 1 to the group address of the device class KNXLight(Light):
""" """Representation of a KNX light."""
_LOGGER.debug("%s: turn on", self.name)
self.set_value('base', [1])
self._state = 1
if ATTR_BRIGHTNESS in kwargs: def __init__(self, hass, device):
self._brightness = kwargs[ATTR_BRIGHTNESS] """Initialization of KNXLight."""
_LOGGER.debug("turn_on requested brightness for light: %s is: %s ", self.device = device
self.name, self._brightness) self.hass = hass
assert self._brightness <= 255 self.async_register_callbacks()
self.set_value("brightness", [self._brightness])
if not self.should_poll: @callback
self.schedule_update_ha_state() def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
def turn_off(self, **kwargs): @property
"""Turn the switch off. def name(self):
"""Return the name of the KNX device."""
return self.device.name
This sends a value 1 to the group address of the device @property
""" def should_poll(self):
_LOGGER.debug("%s: turn off", self.name) """No polling needed within KNX."""
self.set_value('base', [0]) return False
self._state = 0
if not self.should_poll: @property
self.schedule_update_ha_state() def brightness(self):
"""Return the brightness of this light between 0..255."""
return self.device.brightness \
if self.device.supports_dimming else \
None
@property
def xy_color(self):
"""Return the XY color value [float, float]."""
return None
@property
def rgb_color(self):
"""Return the RBG color value."""
return None
@property
def color_temp(self):
"""Return the CT color temperature."""
return None
@property
def white_value(self):
"""Return the white value of this light between 0..255."""
return None
@property
def effect_list(self):
"""Return the list of supported effects."""
return None
@property
def effect(self):
"""Return the current effect."""
return None
@property @property
def is_on(self): def is_on(self):
"""Return True if the value is not 0 is on, else False.""" """Return true if light is on."""
return self._state != 0 return self.device.state
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
return self._supported_features flags = 0
if self.device.supports_dimming:
flags |= SUPPORT_BRIGHTNESS
return flags
def update(self): @asyncio.coroutine
"""Update device state.""" def async_turn_on(self, **kwargs):
super().update() """Turn the light on."""
if self.has_attribute('brightness_state'): if ATTR_BRIGHTNESS in kwargs and self.device.supports_dimming:
value = self.value('brightness_state') yield from self.device.set_brightness(int(kwargs[ATTR_BRIGHTNESS]))
if value is not None: else:
self._brightness = int.from_bytes(value, byteorder='little') yield from self.device.set_on()
_LOGGER.debug("%s: brightness = %d",
self.name, self._brightness)
if self.has_attribute('state'): @asyncio.coroutine
self._state = self.value("state")[0] def async_turn_off(self, **kwargs):
_LOGGER.debug("%s: state = %d", self.name, self._state) """Turn the light off."""
yield from self.device.set_off()
def should_poll(self):
"""No polling needed for a KNX light."""
return False

View file

@ -82,8 +82,6 @@ def async_setup(hass, config):
"""Set up a notify platform.""" """Set up a notify platform."""
if p_config is None: if p_config is None:
p_config = {} p_config = {}
if discovery_info is None:
discovery_info = {}
platform = yield from async_prepare_setup_platform( platform = yield from async_prepare_setup_platform(
hass, config, DOMAIN, p_type) hass, config, DOMAIN, p_type)
@ -105,8 +103,12 @@ def async_setup(hass, config):
raise HomeAssistantError("Invalid notify platform.") raise HomeAssistantError("Invalid notify platform.")
if notify_service is None: if notify_service is None:
_LOGGER.error( # Platforms can decide not to create a service based
"Failed to initialize notification service %s", p_type) # on discovery data.
if discovery_info is None:
_LOGGER.error(
"Failed to initialize notification service %s",
p_type)
return return
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
@ -115,6 +117,9 @@ def async_setup(hass, config):
notify_service.hass = hass notify_service.hass = hass
if discovery_info is None:
discovery_info = {}
@asyncio.coroutine @asyncio.coroutine
def async_notify_message(service): def async_notify_message(service):
"""Handle sending notification message service calls.""" """Handle sending notification message service calls."""

View file

@ -0,0 +1,99 @@
"""
KNX/IP notification service.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/notify.knx/
"""
import asyncio
import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.notify import PLATFORM_SCHEMA, \
BaseNotificationService
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
CONF_ADDRESS = 'address'
DEFAULT_NAME = 'KNX Notify'
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
@asyncio.coroutine
def async_get_service(hass, config, discovery_info=None):
"""Get the KNX notification service."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
return async_get_service_discovery(hass, discovery_info) \
if discovery_info is not None else \
async_get_service_config(hass, config)
@callback
def async_get_service_discovery(hass, discovery_info):
"""Set up notifications for KNX platform configured via xknx.yaml."""
notification_devices = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
notification_devices.append(device)
return \
KNXNotificationService(hass, notification_devices) \
if notification_devices else \
None
@callback
def async_get_service_config(hass, config):
"""Set up notification for KNX platform configured within plattform."""
import xknx
notification = xknx.devices.Notification(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address=config.get(CONF_ADDRESS))
hass.data[DATA_KNX].xknx.devices.add(notification)
return KNXNotificationService(hass, [notification, ])
class KNXNotificationService(BaseNotificationService):
"""Implement demo notification service."""
def __init__(self, hass, devices):
"""Initialize the service."""
self.hass = hass
self.devices = devices
@property
def targets(self):
"""Return a dictionary of registered targets."""
ret = {}
for device in self.devices:
ret[device.name] = device.name
return ret
@asyncio.coroutine
def async_send_message(self, message="", **kwargs):
"""Send a notification to knx bus."""
if "target" in kwargs:
yield from self._async_send_to_device(message, kwargs["target"])
else:
yield from self._async_send_to_all_devices(message)
@asyncio.coroutine
def _async_send_to_all_devices(self, message):
"""Send a notification to knx bus to all connected devices."""
for device in self.devices:
yield from device.set(message)
@asyncio.coroutine
def _async_send_to_device(self, message, names):
"""Send a notification to knx bus to device with given names."""
for device in self.devices:
if device.name in names:
yield from device.set(message)

View file

@ -1,184 +1,111 @@
""" """
Sensors of a KNX Device. Support for KNX/IP sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/knx/ https://home-assistant.io/components/sensor.knx/
""" """
from enum import Enum import asyncio
import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
CONF_NAME, CONF_MAXIMUM, CONF_MINIMUM, from homeassistant.helpers.entity import Entity
CONF_TYPE, TEMP_CELSIUS
)
from homeassistant.components.knx import (KNXConfig, KNXGroupAddress)
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) CONF_ADDRESS = 'address'
CONF_TYPE = 'type'
DEFAULT_NAME = 'KNX Sensor'
DEPENDENCIES = ['knx'] DEPENDENCIES = ['knx']
DEFAULT_NAME = "KNX sensor"
CONF_TEMPERATURE = 'temperature'
CONF_ADDRESS = 'address'
CONF_ILLUMINANCE = 'illuminance'
CONF_PERCENTAGE = 'percentage'
CONF_SPEED_MS = 'speed_ms'
class KNXAddressType(Enum):
"""Enum to indicate conversion type for the KNX address."""
FLOAT = 1
PERCENT = 2
# define the fixed settings required for each sensor type
FIXED_SETTINGS_MAP = {
# Temperature as defined in KNX Standard 3.10 - 9.001 DPT_Value_Temp
CONF_TEMPERATURE: {
'unit': TEMP_CELSIUS,
'default_minimum': -273,
'default_maximum': 670760,
'address_type': KNXAddressType.FLOAT
},
# Speed m/s as defined in KNX Standard 3.10 - 9.005 DPT_Value_Wsp
CONF_SPEED_MS: {
'unit': 'm/s',
'default_minimum': 0,
'default_maximum': 670760,
'address_type': KNXAddressType.FLOAT
},
# Luminance(LUX) as defined in KNX Standard 3.10 - 9.004 DPT_Value_Lux
CONF_ILLUMINANCE: {
'unit': 'lx',
'default_minimum': 0,
'default_maximum': 670760,
'address_type': KNXAddressType.FLOAT
},
# Percentage(%) as defined in KNX Standard 3.10 - 5.001 DPT_Scaling
CONF_PERCENTAGE: {
'unit': '%',
'default_minimum': 0,
'default_maximum': 100,
'address_type': KNXAddressType.PERCENT
}
}
SENSOR_TYPES = set(FIXED_SETTINGS_MAP.keys())
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
vol.Required(CONF_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_MINIMUM): vol.Coerce(float), vol.Optional(CONF_TYPE): cv.string,
vol.Optional(CONF_MAXIMUM): vol.Coerce(float)
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine
"""Set up the KNX Sensor platform.""" def async_setup_platform(hass, config, add_devices,
add_devices([KNXSensor(hass, KNXConfig(config))]) discovery_info=None):
"""Set up sensor(s) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, add_devices)
else:
async_add_devices_config(hass, config, add_devices)
return True
class KNXSensor(KNXGroupAddress): @callback
"""Representation of a KNX Sensor device.""" def async_add_devices_discovery(hass, discovery_info, add_devices):
"""Set up sensors 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(KNXSensor(hass, device))
add_devices(entities)
def __init__(self, hass, config):
"""Initialize a KNX Float Sensor."""
# set up the KNX Group address
KNXGroupAddress.__init__(self, hass, config)
device_type = config.config.get(CONF_TYPE) @callback
sensor_config = FIXED_SETTINGS_MAP.get(device_type) def async_add_devices_config(hass, config, add_devices):
"""Set up sensor for KNX platform configured within plattform."""
import xknx
sensor = xknx.devices.Sensor(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address=config.get(CONF_ADDRESS),
value_type=config.get(CONF_TYPE))
hass.data[DATA_KNX].xknx.devices.add(sensor)
add_devices([KNXSensor(hass, sensor)])
if not sensor_config:
raise NotImplementedError()
# set up the conversion function based on the address type class KNXSensor(Entity):
address_type = sensor_config.get('address_type') """Representation of a KNX sensor."""
if address_type == KNXAddressType.FLOAT:
self.convert = convert_float
elif address_type == KNXAddressType.PERCENT:
self.convert = convert_percent
else:
raise NotImplementedError()
# other settings def __init__(self, hass, device):
self._unit_of_measurement = sensor_config.get('unit') """Initialization of KNXSensor."""
default_min = float(sensor_config.get('default_minimum')) self.device = device
default_max = float(sensor_config.get('default_maximum')) self.hass = hass
self._minimum_value = config.config.get(CONF_MINIMUM, default_min) self.async_register_callbacks()
self._maximum_value = config.config.get(CONF_MAXIMUM, default_max)
_LOGGER.debug(
"%s: configured additional settings: unit=%s, "
"min=%f, max=%f, type=%s",
self.name, self._unit_of_measurement,
self._minimum_value, self._maximum_value, str(address_type)
)
self._value = None @callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property @property
def state(self): def state(self):
"""Return the Value of the KNX Sensor.""" """Return the state of the sensor."""
return self._value return self.device.resolve_state()
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the defined Unit of Measurement for the KNX Sensor.""" """Return the unit this state is expressed in."""
return self._unit_of_measurement return self.device.unit_of_measurement()
def update(self):
"""Update KNX sensor."""
super().update()
self._value = None
if self._data:
if self._data == 0:
value = 0
else:
value = self.convert(self._data)
if self._minimum_value <= value <= self._maximum_value:
self._value = value
@property @property
def cache(self): def device_state_attributes(self):
"""We don't want to cache any Sensor Value.""" """Return the state attributes."""
return False return None
def convert_float(raw_value):
"""Conversion for 2 byte floating point values.
2byte Floating Point KNX Telegram.
Defined in KNX 3.7.2 - 3.10
"""
from knxip.conversion import knx2_to_float
from knxip.core import KNXException
try:
return knx2_to_float(raw_value)
except KNXException as exception:
_LOGGER.error("Can't convert %s to float (%s)", raw_value, exception)
def convert_percent(raw_value):
"""Conversion for scaled byte values.
1byte percentage scaled KNX Telegram.
Defined in KNX 3.7.2 - 3.10.
"""
value = 0
try:
value = raw_value[0]
except (IndexError, ValueError):
# pknx returns a non-iterable type for unsuccessful reads
_LOGGER.error("Can't convert %s to percent value", raw_value)
return round(value * 100 / 255)

View file

@ -1,14 +1,16 @@
""" """
Support KNX switching actuators. Support for KNX/IP switches.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.knx/ https://home-assistant.io/components/switch.knx/
""" """
import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
CONF_ADDRESS = 'address' CONF_ADDRESS = 'address'
@ -24,30 +26,85 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine
"""Set up the KNX switch platform.""" def async_setup_platform(hass, config, add_devices,
add_devices([KNXSwitch(hass, KNXConfig(config))]) discovery_info=None):
"""Set up switch(es) for KNX platform."""
if DATA_KNX not in hass.data \
or not hass.data[DATA_KNX].initialized:
return False
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, add_devices)
else:
async_add_devices_config(hass, config, add_devices)
return True
class KNXSwitch(KNXGroupAddress, SwitchDevice): @callback
"""Representation of a KNX switch device.""" def async_add_devices_discovery(hass, discovery_info, add_devices):
"""Set up switches 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(KNXSwitch(hass, device))
add_devices(entities)
def turn_on(self, **kwargs):
"""Turn the switch on.
This sends a value 0 to the group address of the device @callback
""" def async_add_devices_config(hass, config, add_devices):
self.group_write(1) """Set up switch for KNX platform configured within plattform."""
self._state = [1] import xknx
if not self.should_poll: switch = xknx.devices.Switch(
self.schedule_update_ha_state() hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
group_address=config.get(CONF_ADDRESS),
group_address_state=config.get(CONF_STATE_ADDRESS))
hass.data[DATA_KNX].xknx.devices.add(switch)
add_devices([KNXSwitch(hass, switch)])
def turn_off(self, **kwargs):
"""Turn the switch off.
This sends a value 1 to the group address of the device class KNXSwitch(SwitchDevice):
""" """Representation of a KNX switch."""
self.group_write(0)
self._state = [0] def __init__(self, hass, device):
if not self.should_poll: """Initialization of KNXSwitch."""
self.schedule_update_ha_state() self.device = device
self.hass = hass
self.async_register_callbacks()
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
"""Callback after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property
def is_on(self):
"""Return true if device is on."""
return self.device.state
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the device on."""
yield from self.device.set_on()
@asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Turn the device off."""
yield from self.device.set_off()

View file

@ -362,9 +362,6 @@ jsonrpc-websocket==0.5
# homeassistant.scripts.keyring # homeassistant.scripts.keyring
keyring>=9.3,<10.0 keyring>=9.3,<10.0
# homeassistant.components.knx
knxip==0.5
# homeassistant.components.device_tracker.owntracks # homeassistant.components.device_tracker.owntracks
libnacl==1.5.2 libnacl==1.5.2
@ -1012,6 +1009,9 @@ xbee-helper==0.0.7
# homeassistant.components.sensor.xbox_live # homeassistant.components.sensor.xbox_live
xboxapi==0.1.1 xboxapi==0.1.1
# homeassistant.components.knx
xknx==0.7.13
# homeassistant.components.media_player.bluesound # homeassistant.components.media_player.bluesound
# homeassistant.components.sensor.swiss_hydrological_data # homeassistant.components.sensor.swiss_hydrological_data
# homeassistant.components.sensor.ted5000 # homeassistant.components.sensor.ted5000