Use centralized KnxEntity for all KNX platforms (#40381)

This commit is contained in:
Marvin Wichmann 2020-09-21 18:08:35 +02:00 committed by GitHub
parent c13fbf795d
commit 3d6434be75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 206 additions and 338 deletions

View file

@ -29,7 +29,7 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from .const import DATA_KNX, DOMAIN, SupportedPlatforms from .const import DOMAIN, SupportedPlatforms
from .factory import create_knx_device from .factory import create_knx_device
from .schema import ( from .schema import (
BinarySensorSchema, BinarySensorSchema,
@ -139,9 +139,9 @@ SERVICE_KNX_SEND_SCHEMA = vol.Schema(
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the KNX component.""" """Set up the KNX component."""
try: try:
hass.data[DATA_KNX] = KNXModule(hass, config) hass.data[DOMAIN] = KNXModule(hass, config)
hass.data[DATA_KNX].async_create_exposures() hass.data[DOMAIN].async_create_exposures()
await hass.data[DATA_KNX].start() await hass.data[DOMAIN].start()
except XKNXException as ex: except XKNXException as ex:
_LOGGER.warning("Could not connect to KNX interface: %s", ex) _LOGGER.warning("Could not connect to KNX interface: %s", ex)
hass.components.persistent_notification.async_create( hass.components.persistent_notification.async_create(
@ -151,7 +151,7 @@ async def async_setup(hass, config):
for platform in SupportedPlatforms: for platform in SupportedPlatforms:
if platform.value in config[DOMAIN]: if platform.value in config[DOMAIN]:
for device_config in config[DOMAIN][platform.value]: for device_config in config[DOMAIN][platform.value]:
create_knx_device(platform, hass.data[DATA_KNX].xknx, device_config) create_knx_device(platform, hass.data[DOMAIN].xknx, device_config)
# We need to wait until all entities are loaded into the device list since they could also be created from other platforms # We need to wait until all entities are loaded into the device list since they could also be created from other platforms
for platform in SupportedPlatforms: for platform in SupportedPlatforms:
@ -159,7 +159,7 @@ async def async_setup(hass, config):
discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config)
) )
if not hass.data[DATA_KNX].xknx.devices: if not hass.data[DOMAIN].xknx.devices:
_LOGGER.warning( _LOGGER.warning(
"No KNX devices are configured. Please read " "No KNX devices are configured. Please read "
"https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes"
@ -168,7 +168,7 @@ async def async_setup(hass, config):
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_KNX_SEND, SERVICE_KNX_SEND,
hass.data[DATA_KNX].service_send_to_knx_bus, hass.data[DOMAIN].service_send_to_knx_bus,
schema=SERVICE_KNX_SEND_SCHEMA, schema=SERVICE_KNX_SEND_SCHEMA,
) )

View file

@ -3,72 +3,41 @@ from typing import Any, Dict, Optional
from xknx.devices import BinarySensor as XknxBinarySensor from xknx.devices import BinarySensor as XknxBinarySensor
from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity
from homeassistant.core import callback
from .const import ATTR_COUNTER, DATA_KNX from .const import ATTR_COUNTER, DOMAIN
from .knx_entity import KnxEntity
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up binary sensor(s) for KNX platform.""" """Set up binary sensor(s) for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxBinarySensor): if isinstance(device, XknxBinarySensor):
entities.append(KNXBinarySensor(device)) entities.append(KNXBinarySensor(device))
async_add_entities(entities) async_add_entities(entities)
class KNXBinarySensor(BinarySensorEntity): class KNXBinarySensor(KnxEntity, BinarySensorEntity):
"""Representation of a KNX binary sensor.""" """Representation of a KNX binary sensor."""
def __init__(self, device: XknxBinarySensor): def __init__(self, device: XknxBinarySensor):
"""Initialize of KNX binary sensor.""" """Initialize of KNX binary sensor."""
self.device = device super().__init__(device)
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device: XknxBinarySensor):
"""Call after device was updated."""
self.async_write_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
async def async_update(self):
"""Request a state update from KNX bus."""
await self.device.sync()
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self):
"""Return True if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property @property
def device_class(self): def device_class(self):
"""Return the class of this sensor.""" """Return the class of this sensor."""
return self.device.device_class if self._device.device_class in DEVICE_CLASSES:
return self._device.device_class
return None
@property @property
def is_on(self): def is_on(self):
"""Return true if the binary sensor is on.""" """Return true if the binary sensor is on."""
return self.device.is_on() return self._device.is_on()
@property @property
def device_state_attributes(self) -> Optional[Dict[str, Any]]: def device_state_attributes(self) -> Optional[Dict[str, Any]]:
"""Return device specific state attributes.""" """Return device specific state attributes."""
return {ATTR_COUNTER: self.device.counter} return {ATTR_COUNTER: self._device.counter}

View file

@ -14,8 +14,8 @@ from homeassistant.components.climate.const import (
) )
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DATA_KNX from .const import DOMAIN, OPERATION_MODES, PRESET_MODES
from .const import OPERATION_MODES, PRESET_MODES from .knx_entity import KnxEntity
OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items()) OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items())
PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items()) PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items())
@ -24,18 +24,19 @@ PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items())
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up climate(s) for KNX platform.""" """Set up climate(s) for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxClimate): if isinstance(device, XknxClimate):
entities.append(KNXClimate(device)) entities.append(KNXClimate(device))
async_add_entities(entities) async_add_entities(entities)
class KNXClimate(ClimateEntity): class KNXClimate(KnxEntity, ClimateEntity):
"""Representation of a KNX climate device.""" """Representation of a KNX climate device."""
def __init__(self, device: XknxClimate): def __init__(self, device: XknxClimate):
"""Initialize of a KNX climate device.""" """Initialize of a KNX climate device."""
self.device = device super().__init__(device)
self._unit_of_measurement = TEMP_CELSIUS self._unit_of_measurement = TEMP_CELSIUS
@property @property
@ -43,35 +44,10 @@ class KNXClimate(ClimateEntity):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
async def async_added_to_hass(self) -> None:
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
self.async_write_ha_state()
self.device.register_device_updated_cb(after_update_callback)
self.device.mode.register_device_updated_cb(after_update_callback)
async def async_update(self): async def async_update(self):
"""Request a state update from KNX bus.""" """Request a state update from KNX bus."""
await self.device.sync() await self._device.sync()
await self.device.mode.sync() await self._device.mode.sync()
@property
def name(self) -> str:
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self) -> bool:
"""No polling needed within KNX."""
return False
@property @property
def temperature_unit(self): def temperature_unit(self):
@ -81,44 +57,44 @@ class KNXClimate(ClimateEntity):
@property @property
def current_temperature(self): def current_temperature(self):
"""Return the current temperature.""" """Return the current temperature."""
return self.device.temperature.value return self._device.temperature.value
@property @property
def target_temperature_step(self): def target_temperature_step(self):
"""Return the supported step of target temperature.""" """Return the supported step of target temperature."""
return self.device.temperature_step return self._device.temperature_step
@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.device.target_temperature.value return self._device.target_temperature.value
@property @property
def min_temp(self): def min_temp(self):
"""Return the minimum temperature.""" """Return the minimum temperature."""
return self.device.target_temperature_min return self._device.target_temperature_min
@property @property
def max_temp(self): def max_temp(self):
"""Return the maximum temperature.""" """Return the maximum temperature."""
return self.device.target_temperature_max return self._device.target_temperature_max
async def async_set_temperature(self, **kwargs) -> None: async def async_set_temperature(self, **kwargs) -> None:
"""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
await self.device.set_target_temperature(temperature) await self._device.set_target_temperature(temperature)
self.async_write_ha_state() self.async_write_ha_state()
@property @property
def hvac_mode(self) -> Optional[str]: def hvac_mode(self) -> Optional[str]:
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
if self.device.supports_on_off and not self.device.is_on: if self._device.supports_on_off and not self._device.is_on:
return HVAC_MODE_OFF return HVAC_MODE_OFF
if self.device.mode.supports_operation_mode: if self._device.mode.supports_operation_mode:
return OPERATION_MODES.get( return OPERATION_MODES.get(
self.device.mode.operation_mode.value, HVAC_MODE_HEAT self._device.mode.operation_mode.value, HVAC_MODE_HEAT
) )
# default to "heat" # default to "heat"
return HVAC_MODE_HEAT return HVAC_MODE_HEAT
@ -128,10 +104,10 @@ class KNXClimate(ClimateEntity):
"""Return the list of available operation modes.""" """Return the list of available operation modes."""
_operations = [ _operations = [
OPERATION_MODES.get(operation_mode.value) OPERATION_MODES.get(operation_mode.value)
for operation_mode in self.device.mode.operation_modes for operation_mode in self._device.mode.operation_modes
] ]
if self.device.supports_on_off: if self._device.supports_on_off:
if not _operations: if not _operations:
_operations.append(HVAC_MODE_HEAT) _operations.append(HVAC_MODE_HEAT)
_operations.append(HVAC_MODE_OFF) _operations.append(HVAC_MODE_OFF)
@ -142,16 +118,16 @@ class KNXClimate(ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set operation mode.""" """Set operation mode."""
if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF: if self._device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
await self.device.turn_off() await self._device.turn_off()
else: else:
if self.device.supports_on_off and not self.device.is_on: if self._device.supports_on_off and not self._device.is_on:
await self.device.turn_on() await self._device.turn_on()
if self.device.mode.supports_operation_mode: if self._device.mode.supports_operation_mode:
knx_operation_mode = HVACOperationMode( knx_operation_mode = HVACOperationMode(
OPERATION_MODES_INV.get(hvac_mode) OPERATION_MODES_INV.get(hvac_mode)
) )
await self.device.mode.set_operation_mode(knx_operation_mode) await self._device.mode.set_operation_mode(knx_operation_mode)
self.async_write_ha_state() self.async_write_ha_state()
@property @property
@ -160,8 +136,8 @@ class KNXClimate(ClimateEntity):
Requires SUPPORT_PRESET_MODE. Requires SUPPORT_PRESET_MODE.
""" """
if self.device.mode.supports_operation_mode: if self._device.mode.supports_operation_mode:
return PRESET_MODES.get(self.device.mode.operation_mode.value, PRESET_AWAY) return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY)
return None return None
@property @property
@ -172,14 +148,14 @@ class KNXClimate(ClimateEntity):
""" """
_presets = [ _presets = [
PRESET_MODES.get(operation_mode.value) PRESET_MODES.get(operation_mode.value)
for operation_mode in self.device.mode.operation_modes for operation_mode in self._device.mode.operation_modes
] ]
return list(filter(None, _presets)) return list(filter(None, _presets))
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode.""" """Set new preset mode."""
if self.device.mode.supports_operation_mode: if self._device.mode.supports_operation_mode:
knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode)) knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode))
await self.device.mode.set_operation_mode(knx_operation_mode) await self._device.mode.set_operation_mode(knx_operation_mode)
self.async_write_ha_state() self.async_write_ha_state()

View file

@ -15,7 +15,6 @@ from homeassistant.components.climate.const import (
) )
DOMAIN = "knx" DOMAIN = "knx"
DATA_KNX = "data_knx"
CONF_STATE_ADDRESS = "state_address" CONF_STATE_ADDRESS = "state_address"
CONF_SYNC_STATE = "sync_state" CONF_SYNC_STATE = "sync_state"

View file

@ -15,65 +15,39 @@ from homeassistant.components.cover import (
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.helpers.event import async_track_utc_time_change
from . import DATA_KNX from .const import DOMAIN
from .knx_entity import KnxEntity
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up cover(s) for KNX platform.""" """Set up cover(s) for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxCover): if isinstance(device, XknxCover):
entities.append(KNXCover(device)) entities.append(KNXCover(device))
async_add_entities(entities) async_add_entities(entities)
class KNXCover(CoverEntity): class KNXCover(KnxEntity, CoverEntity):
"""Representation of a KNX cover.""" """Representation of a KNX cover."""
def __init__(self, device: XknxCover): def __init__(self, device: XknxCover):
"""Initialize the cover.""" """Initialize the cover."""
self.device = device super().__init__(device)
self._unsubscribe_auto_updater = None self._unsubscribe_auto_updater = None
@callback @callback
def async_register_callbacks(self): async def after_update_callback(self, device):
"""Register callbacks to update hass after device was changed.""" """Call after device was updated."""
self.async_write_ha_state()
async def after_update_callback(device): if self._device.is_traveling():
"""Call after device was updated.""" self.start_auto_updater()
self.async_write_ha_state()
if self.device.is_traveling():
self.start_auto_updater()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
async def async_update(self):
"""Request a state update from KNX bus."""
await self.device.sync()
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self):
"""Return True if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property @property
def device_class(self): def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""
if self.device.supports_angle: if self._device.supports_angle:
return DEVICE_CLASS_BLIND return DEVICE_CLASS_BLIND
return None return None
@ -81,9 +55,9 @@ class KNXCover(CoverEntity):
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
if self.device.supports_stop: if self._device.supports_stop:
supported_features |= SUPPORT_STOP supported_features |= SUPPORT_STOP
if self.device.supports_angle: if self._device.supports_angle:
supported_features |= SUPPORT_SET_TILT_POSITION supported_features |= SUPPORT_SET_TILT_POSITION
return supported_features return supported_features
@ -95,57 +69,57 @@ class KNXCover(CoverEntity):
""" """
# In KNX 0 is open, 100 is closed. # In KNX 0 is open, 100 is closed.
try: try:
return 100 - self.device.current_position() return 100 - self._device.current_position()
except TypeError: except TypeError:
return None return None
@property @property
def is_closed(self): def is_closed(self):
"""Return if the cover is closed.""" """Return if the cover is closed."""
return self.device.is_closed() return self._device.is_closed()
@property @property
def is_opening(self): def is_opening(self):
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self.device.is_opening() return self._device.is_opening()
@property @property
def is_closing(self): def is_closing(self):
"""Return if the cover is closing or not.""" """Return if the cover is closing or not."""
return self.device.is_closing() return self._device.is_closing()
async def async_close_cover(self, **kwargs): async def async_close_cover(self, **kwargs):
"""Close the cover.""" """Close the cover."""
await self.device.set_down() await self._device.set_down()
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""
await self.device.set_up() await self._device.set_up()
async def async_set_cover_position(self, **kwargs): async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
knx_position = 100 - kwargs[ATTR_POSITION] knx_position = 100 - kwargs[ATTR_POSITION]
await self.device.set_position(knx_position) await self._device.set_position(knx_position)
async def async_stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs):
"""Stop the cover.""" """Stop the cover."""
await self.device.stop() await self._device.stop()
self.stop_auto_updater() self.stop_auto_updater()
@property @property
def current_cover_tilt_position(self): def current_cover_tilt_position(self):
"""Return current tilt position of cover.""" """Return current tilt position of cover."""
if not self.device.supports_angle: if not self._device.supports_angle:
return None return None
try: try:
return 100 - self.device.current_angle() return 100 - self._device.current_angle()
except TypeError: except TypeError:
return None return None
async def async_set_cover_tilt_position(self, **kwargs): async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position.""" """Move the cover tilt to a specific position."""
knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION] knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION]
await self.device.set_angle(knx_tilt_position) await self._device.set_angle(knx_tilt_position)
def start_auto_updater(self): def start_auto_updater(self):
"""Start the autoupdater to update Home Assistant while cover is moving.""" """Start the autoupdater to update Home Assistant while cover is moving."""
@ -164,7 +138,7 @@ class KNXCover(CoverEntity):
def auto_updater_hook(self, now): def auto_updater_hook(self, now):
"""Call for the autoupdater.""" """Call for the autoupdater."""
self.async_write_ha_state() self.async_write_ha_state()
if self.device.position_reached(): if self._device.position_reached():
self.stop_auto_updater() self.stop_auto_updater()
self.hass.add_job(self.device.auto_stop_if_necessary()) self.hass.add_job(self._device.auto_stop_if_necessary())

View file

@ -0,0 +1,51 @@
"""Base class for KNX devices."""
from xknx.devices import Climate as XknxClimate, Device as XknxDevice
from homeassistant.helpers.entity import Entity
from .const import DOMAIN
class KnxEntity(Entity):
"""Representation of a KNX entity."""
def __init__(self, device: XknxDevice):
"""Set up device."""
self._device = device
@property
def name(self):
"""Return the name of the KNX device."""
return self._device.name
@property
def available(self):
"""Return True if entity is available."""
return self.hass.data[DOMAIN].connected
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
async def async_update(self):
"""Request a state update from KNX bus."""
await self._device.sync()
async def after_update_callback(self, device: XknxDevice):
"""Call after device was updated."""
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Store register state change callback."""
self._device.register_device_updated_cb(self.after_update_callback)
if isinstance(self._device, XknxClimate):
self._device.mode.register_device_updated_cb(self.after_update_callback)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed."""
self._device.unregister_device_updated_cb(self.after_update_callback)
if isinstance(self._device, XknxClimate):
self._device.mode.unregister_device_updated_cb(self.after_update_callback)

View file

@ -12,10 +12,10 @@ from homeassistant.components.light import (
SUPPORT_WHITE_VALUE, SUPPORT_WHITE_VALUE,
LightEntity, LightEntity,
) )
from homeassistant.core import callback
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from . import DATA_KNX from .const import DOMAIN
from .knx_entity import KnxEntity
DEFAULT_COLOR = (0.0, 0.0) DEFAULT_COLOR = (0.0, 0.0)
DEFAULT_BRIGHTNESS = 255 DEFAULT_BRIGHTNESS = 255
@ -25,18 +25,18 @@ DEFAULT_WHITE_VALUE = 255
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up lights for KNX platform.""" """Set up lights for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxLight): if isinstance(device, XknxLight):
entities.append(KNXLight(device)) entities.append(KNXLight(device))
async_add_entities(entities) async_add_entities(entities)
class KNXLight(LightEntity): class KNXLight(KnxEntity, LightEntity):
"""Representation of a KNX light.""" """Representation of a KNX light."""
def __init__(self, device: XknxLight): def __init__(self, device: XknxLight):
"""Initialize of KNX light.""" """Initialize of KNX light."""
self.device = device super().__init__(device)
self._min_kelvin = device.min_kelvin self._min_kelvin = device.min_kelvin
self._max_kelvin = device.max_kelvin self._max_kelvin = device.max_kelvin
@ -47,46 +47,13 @@ class KNXLight(LightEntity):
self._min_kelvin self._min_kelvin
) )
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
self.async_write_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
async def async_update(self):
"""Request a state update from KNX bus."""
await self.device.sync()
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self):
"""Return True if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property @property
def brightness(self): def brightness(self):
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
if self.device.supports_brightness: if self._device.supports_brightness:
return self.device.current_brightness return self._device.current_brightness
hsv_color = self._hsv_color hsv_color = self._hsv_color
if self.device.supports_color and hsv_color: if self._device.supports_color and hsv_color:
return round(hsv_color[-1] / 100 * 255) return round(hsv_color[-1] / 100 * 255)
return None return None
@ -94,35 +61,35 @@ class KNXLight(LightEntity):
def hs_color(self): def hs_color(self):
"""Return the HS color value.""" """Return the HS color value."""
rgb = None rgb = None
if self.device.supports_rgbw or self.device.supports_color: if self._device.supports_rgbw or self._device.supports_color:
rgb, _ = self.device.current_color rgb, _ = self._device.current_color
return color_util.color_RGB_to_hs(*rgb) if rgb else None return color_util.color_RGB_to_hs(*rgb) if rgb else None
@property @property
def _hsv_color(self): def _hsv_color(self):
"""Return the HSV color value.""" """Return the HSV color value."""
rgb = None rgb = None
if self.device.supports_rgbw or self.device.supports_color: if self._device.supports_rgbw or self._device.supports_color:
rgb, _ = self.device.current_color rgb, _ = self._device.current_color
return color_util.color_RGB_to_hsv(*rgb) if rgb else None return color_util.color_RGB_to_hsv(*rgb) if rgb else None
@property @property
def white_value(self): def white_value(self):
"""Return the white value.""" """Return the white value."""
white = None white = None
if self.device.supports_rgbw: if self._device.supports_rgbw:
_, white = self.device.current_color _, white = self._device.current_color
return white return white
@property @property
def color_temp(self): def color_temp(self):
"""Return the color temperature in mireds.""" """Return the color temperature in mireds."""
if self.device.supports_color_temperature: if self._device.supports_color_temperature:
kelvin = self.device.current_color_temperature kelvin = self._device.current_color_temperature
if kelvin is not None: if kelvin is not None:
return color_util.color_temperature_kelvin_to_mired(kelvin) return color_util.color_temperature_kelvin_to_mired(kelvin)
if self.device.supports_tunable_white: if self._device.supports_tunable_white:
relative_ct = self.device.current_tunable_white relative_ct = self._device.current_tunable_white
if relative_ct is not None: if relative_ct is not None:
# as KNX devices typically use Kelvin we use it as base for # as KNX devices typically use Kelvin we use it as base for
# calculating ct from percent # calculating ct from percent
@ -155,19 +122,22 @@ class KNXLight(LightEntity):
@property @property
def is_on(self): def is_on(self):
"""Return true if light is on.""" """Return true if light is on."""
return self.device.state return self._device.state
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
flags = 0 flags = 0
if self.device.supports_brightness: if self._device.supports_brightness:
flags |= SUPPORT_BRIGHTNESS flags |= SUPPORT_BRIGHTNESS
if self.device.supports_color: if self._device.supports_color:
flags |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS flags |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS
if self.device.supports_rgbw: if self._device.supports_rgbw:
flags |= SUPPORT_COLOR | SUPPORT_WHITE_VALUE flags |= SUPPORT_COLOR | SUPPORT_WHITE_VALUE
if self.device.supports_color_temperature or self.device.supports_tunable_white: if (
self._device.supports_color_temperature
or self._device.supports_tunable_white
):
flags |= SUPPORT_COLOR_TEMP flags |= SUPPORT_COLOR_TEMP
return flags return flags
@ -191,14 +161,16 @@ class KNXLight(LightEntity):
or update_white_value or update_white_value
or update_color_temp or update_color_temp
): ):
await self.device.set_on() await self._device.set_on()
if self.device.supports_brightness and (update_brightness and not update_color): if self._device.supports_brightness and (
update_brightness and not update_color
):
# if we don't need to update the color, try updating brightness # if we don't need to update the color, try updating brightness
# directly if supported; don't do it if color also has to be # directly if supported; don't do it if color also has to be
# changed, as RGB color implicitly sets the brightness as well # changed, as RGB color implicitly sets the brightness as well
await self.device.set_brightness(brightness) await self._device.set_brightness(brightness)
elif (self.device.supports_rgbw or self.device.supports_color) and ( elif (self._device.supports_rgbw or self._device.supports_color) and (
update_brightness or update_color or update_white_value update_brightness or update_color or update_white_value
): ):
# change RGB color, white value (if supported), and brightness # change RGB color, white value (if supported), and brightness
@ -208,25 +180,25 @@ class KNXLight(LightEntity):
brightness = DEFAULT_BRIGHTNESS brightness = DEFAULT_BRIGHTNESS
if hs_color is None: if hs_color is None:
hs_color = DEFAULT_COLOR hs_color = DEFAULT_COLOR
if white_value is None and self.device.supports_rgbw: if white_value is None and self._device.supports_rgbw:
white_value = DEFAULT_WHITE_VALUE white_value = DEFAULT_WHITE_VALUE
rgb = color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255) rgb = color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255)
await self.device.set_color(rgb, white_value) await self._device.set_color(rgb, white_value)
if update_color_temp: if update_color_temp:
kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds)) kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds))
kelvin = min(self._max_kelvin, max(self._min_kelvin, kelvin)) kelvin = min(self._max_kelvin, max(self._min_kelvin, kelvin))
if self.device.supports_color_temperature: if self._device.supports_color_temperature:
await self.device.set_color_temperature(kelvin) await self._device.set_color_temperature(kelvin)
elif self.device.supports_tunable_white: elif self._device.supports_tunable_white:
relative_ct = int( relative_ct = int(
255 255
* (kelvin - self._min_kelvin) * (kelvin - self._min_kelvin)
/ (self._max_kelvin - self._min_kelvin) / (self._max_kelvin - self._min_kelvin)
) )
await self.device.set_tunable_white(relative_ct) await self._device.set_tunable_white(relative_ct)
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the light off.""" """Turn the light off."""
await self.device.set_off() await self._device.set_off()

View file

@ -5,13 +5,13 @@ from xknx.devices import Notification as XknxNotification
from homeassistant.components.notify import BaseNotificationService from homeassistant.components.notify import BaseNotificationService
from . import DATA_KNX from .const import DOMAIN
async def async_get_service(hass, config, discovery_info=None): async def async_get_service(hass, config, discovery_info=None):
"""Get the KNX notification service.""" """Get the KNX notification service."""
notification_devices = [] notification_devices = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxNotification): if isinstance(device, XknxNotification):
notification_devices.append(device) notification_devices.append(device)
return ( return (

View file

@ -5,30 +5,26 @@ from xknx.devices import Scene as XknxScene
from homeassistant.components.scene import Scene from homeassistant.components.scene import Scene
from . import DATA_KNX from .const import DOMAIN
from .knx_entity import KnxEntity
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the scenes for KNX platform.""" """Set up the scenes for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxScene): if isinstance(device, XknxScene):
entities.append(KNXScene(device)) entities.append(KNXScene(device))
async_add_entities(entities) async_add_entities(entities)
class KNXScene(Scene): class KNXScene(KnxEntity, Scene):
"""Representation of a KNX scene.""" """Representation of a KNX scene."""
def __init__(self, scene: XknxScene): def __init__(self, device: XknxScene):
"""Init KNX scene.""" """Init KNX scene."""
self.scene = scene super().__init__(device)
@property
def name(self):
"""Return the name of the scene."""
return self.scene.name
async def async_activate(self, **kwargs: Any) -> None: async def async_activate(self, **kwargs: Any) -> None:
"""Activate the scene.""" """Activate the scene."""
await self.scene.run() await self._device.run()

View file

@ -1,77 +1,43 @@
"""Support for KNX/IP sensors.""" """Support for KNX/IP sensors."""
from xknx.devices import Sensor as XknxSensor from xknx.devices import Sensor as XknxSensor
from homeassistant.core import callback from homeassistant.components.sensor import DEVICE_CLASSES
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from . import DATA_KNX from .const import DOMAIN
from .knx_entity import KnxEntity
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up sensor(s) for KNX platform.""" """Set up sensor(s) for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxSensor): if isinstance(device, XknxSensor):
entities.append(KNXSensor(device)) entities.append(KNXSensor(device))
async_add_entities(entities) async_add_entities(entities)
class KNXSensor(Entity): class KNXSensor(KnxEntity, Entity):
"""Representation of a KNX sensor.""" """Representation of a KNX sensor."""
def __init__(self, device: XknxSensor): def __init__(self, device: XknxSensor):
"""Initialize of a KNX sensor.""" """Initialize of a KNX sensor."""
self.device = device super().__init__(device)
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
self.async_write_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
async def async_update(self):
"""Update the state from KNX."""
await self.device.sync()
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self):
"""Return True if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self):
"""No polling needed within KNX."""
return False
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.device.resolve_state() return self._device.resolve_state()
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit this state is expressed in.""" """Return the unit this state is expressed in."""
return self.device.unit_of_measurement() return self._device.unit_of_measurement()
@property @property
def device_class(self): def device_class(self):
"""Return the device class of the sensor.""" """Return the device class of the sensor."""
return self.device.ha_device_class() device_class = self._device.ha_device_class()
if device_class in DEVICE_CLASSES:
@property return device_class
def device_state_attributes(self):
"""Return the state attributes."""
return None return None

View file

@ -2,69 +2,36 @@
from xknx.devices import Switch as XknxSwitch from xknx.devices import Switch as XknxSwitch
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.core import callback
from . import DATA_KNX from . import DOMAIN
from .knx_entity import KnxEntity
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up switch(es) for KNX platform.""" """Set up switch(es) for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxSwitch): if isinstance(device, XknxSwitch):
entities.append(KNXSwitch(device)) entities.append(KNXSwitch(device))
async_add_entities(entities) async_add_entities(entities)
class KNXSwitch(SwitchEntity): class KNXSwitch(KnxEntity, SwitchEntity):
"""Representation of a KNX switch.""" """Representation of a KNX switch."""
def __init__(self, device: XknxSwitch): def __init__(self, device: XknxSwitch):
"""Initialize of KNX switch.""" """Initialize of KNX switch."""
self.device = device super().__init__(device)
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
self.async_write_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
async def async_update(self):
"""Request a state update from KNX bus."""
await self.device.sync()
@property
def name(self):
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self):
"""Return true if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self):
"""Return the polling state. Not needed within KNX."""
return False
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""
return self.device.state return self._device.state
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the device on."""
await self.device.set_on() await self._device.set_on()
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the device off.""" """Turn the device off."""
await self.device.set_off() await self._device.set_off()

View file

@ -5,34 +5,30 @@ from xknx.devices import Weather as XknxWeather
from homeassistant.components.weather import WeatherEntity from homeassistant.components.weather import WeatherEntity
from homeassistant.const import TEMP_CELSIUS from homeassistant.const import TEMP_CELSIUS
from .const import DATA_KNX from .const import DOMAIN
from .knx_entity import KnxEntity
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the scenes for KNX platform.""" """Set up the scenes for KNX platform."""
entities = [] entities = []
for device in hass.data[DATA_KNX].xknx.devices: for device in hass.data[DOMAIN].xknx.devices:
if isinstance(device, XknxWeather): if isinstance(device, XknxWeather):
entities.append(KNXWeather(device)) entities.append(KNXWeather(device))
async_add_entities(entities) async_add_entities(entities)
class KNXWeather(WeatherEntity): class KNXWeather(KnxEntity, WeatherEntity):
"""Representation of a KNX weather device.""" """Representation of a KNX weather device."""
def __init__(self, device: XknxWeather): def __init__(self, device: XknxWeather):
"""Initialize of a KNX sensor.""" """Initialize of a KNX sensor."""
self.device = device super().__init__(device)
@property
def name(self):
"""Return the name of the weather device."""
return self.device.name
@property @property
def temperature(self): def temperature(self):
"""Return current temperature.""" """Return current temperature."""
return self.device.temperature return self._device.temperature
@property @property
def temperature_unit(self): def temperature_unit(self):
@ -44,25 +40,27 @@ class KNXWeather(WeatherEntity):
"""Return current air pressure.""" """Return current air pressure."""
# KNX returns pA - HA requires hPa # KNX returns pA - HA requires hPa
return ( return (
self.device.air_pressure / 100 self._device.air_pressure / 100
if self.device.air_pressure is not None if self._device.air_pressure is not None
else None else None
) )
@property @property
def condition(self): def condition(self):
"""Return current weather condition.""" """Return current weather condition."""
return self.device.ha_current_state().value return self._device.ha_current_state().value
@property @property
def humidity(self): def humidity(self):
"""Return current humidity.""" """Return current humidity."""
return self.device.humidity if self.device.humidity is not None else None return self._device.humidity if self._device.humidity is not None else None
@property @property
def wind_speed(self): def wind_speed(self):
"""Return current wind speed in km/h.""" """Return current wind speed in km/h."""
# KNX only supports wind speed in m/s # KNX only supports wind speed in m/s
return ( return (
self.device.wind_speed * 3.6 if self.device.wind_speed is not None else None self._device.wind_speed * 3.6
if self._device.wind_speed is not None
else None
) )