core/homeassistant/components/knx/expose.py

155 lines
4.6 KiB
Python

"""Exposures to KNX bus."""
from typing import Union
from xknx import XKNX
from xknx.devices import DateTime, ExposeSensor
from homeassistant.const import (
CONF_ADDRESS,
CONF_ENTITY_ID,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType
from .schema import ExposeSchema
@callback
def create_knx_exposure(
hass: HomeAssistant, xknx: XKNX, config: ConfigType
) -> Union["KNXExposeSensor", "KNXExposeTime"]:
"""Create exposures from config."""
address = config[CONF_ADDRESS]
attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE)
entity_id = config.get(CONF_ENTITY_ID)
expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE)
default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT)
exposure: Union["KNXExposeSensor", "KNXExposeTime"]
if expose_type.lower() in ["time", "date", "datetime"]:
exposure = KNXExposeTime(xknx, expose_type, address)
else:
exposure = KNXExposeSensor(
hass,
xknx,
expose_type,
entity_id,
attribute,
default,
address,
)
exposure.async_register()
return exposure
class KNXExposeSensor:
"""Object to Expose Home Assistant entity to KNX bus."""
def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address):
"""Initialize of Expose class."""
self.hass = hass
self.xknx = xknx
self.type = expose_type
self.entity_id = entity_id
self.expose_attribute = attribute
self.expose_default = default
self.address = address
self.device = None
self._remove_listener = None
@callback
def async_register(self):
"""Register listener."""
if self.expose_attribute is not None:
_name = self.entity_id + "__" + self.expose_attribute
else:
_name = self.entity_id
self.device = ExposeSensor(
self.xknx,
name=_name,
group_address=self.address,
value_type=self.type,
)
self._remove_listener = async_track_state_change_event(
self.hass, [self.entity_id], self._async_entity_changed
)
@callback
def shutdown(self) -> None:
"""Prepare for deletion."""
if self._remove_listener is not None:
self._remove_listener()
self._remove_listener = None
if self.device is not None:
self.device.shutdown()
async def _async_entity_changed(self, event):
"""Handle entity change."""
new_state = event.data.get("new_state")
if new_state is None:
return
if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
return
if self.expose_attribute is None:
await self._async_set_knx_value(new_state.state)
return
new_attribute = new_state.attributes.get(self.expose_attribute)
old_state = event.data.get("old_state")
if old_state is not None:
old_attribute = old_state.attributes.get(self.expose_attribute)
if old_attribute == new_attribute:
# don't send same value sequentially
return
await self._async_set_knx_value(new_attribute)
async def _async_set_knx_value(self, value):
"""Set new value on xknx ExposeSensor."""
if value is None:
if self.expose_default is None:
return
value = self.expose_default
if self.type == "binary":
if value == STATE_ON:
value = True
elif value == STATE_OFF:
value = False
await self.device.set(value)
class KNXExposeTime:
"""Object to Expose Time/Date object to KNX bus."""
def __init__(self, xknx: XKNX, expose_type: str, address: str):
"""Initialize of Expose class."""
self.xknx = xknx
self.expose_type = expose_type
self.address = address
self.device = None
@callback
def async_register(self):
"""Register listener."""
self.device = DateTime(
self.xknx,
name=self.expose_type.capitalize(),
broadcast_type=self.expose_type.upper(),
localtime=True,
group_address=self.address,
)
@callback
def shutdown(self):
"""Prepare for deletion."""
if self.device is not None:
self.device.shutdown()