2019-02-14 04:35:12 +00:00
|
|
|
"""Support KNX devices."""
|
2018-01-21 06:35:38 +00:00
|
|
|
import logging
|
2016-07-10 17:36:54 +00:00
|
|
|
|
2016-09-14 06:03:30 +00:00
|
|
|
import voluptuous as vol
|
2019-10-22 05:38:21 +00:00
|
|
|
from xknx import XKNX
|
2020-08-26 16:03:03 +00:00
|
|
|
from xknx.devices import DateTime, ExposeSensor
|
|
|
|
from xknx.dpt import DPTArray, DPTBase, DPTBinary
|
2019-10-22 05:38:21 +00:00
|
|
|
from xknx.exceptions import XKNXException
|
|
|
|
from xknx.io import DEFAULT_MCAST_PORT, ConnectionConfig, ConnectionType
|
2020-05-13 13:19:00 +00:00
|
|
|
from xknx.telegram import AddressFilter, GroupAddress, Telegram
|
2016-09-14 06:03:30 +00:00
|
|
|
|
2018-02-26 07:44:09 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_ENTITY_ID,
|
|
|
|
CONF_HOST,
|
|
|
|
CONF_PORT,
|
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
2020-05-13 13:19:00 +00:00
|
|
|
STATE_OFF,
|
|
|
|
STATE_ON,
|
|
|
|
STATE_UNAVAILABLE,
|
|
|
|
STATE_UNKNOWN,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-02-26 07:44:09 +00:00
|
|
|
from homeassistant.core import callback
|
2017-09-07 07:11:55 +00:00
|
|
|
from homeassistant.helpers import discovery
|
2017-04-30 05:04:49 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2020-07-15 06:37:25 +00:00
|
|
|
from homeassistant.helpers.event import async_track_state_change_event
|
2020-08-26 16:03:03 +00:00
|
|
|
|
2020-08-30 18:13:47 +00:00
|
|
|
from .const import DATA_KNX, DOMAIN, SupportedPlatforms
|
2020-08-26 16:03:03 +00:00
|
|
|
from .factory import create_knx_device
|
|
|
|
from .schema import (
|
|
|
|
BinarySensorSchema,
|
|
|
|
ClimateSchema,
|
|
|
|
ConnectionSchema,
|
|
|
|
CoverSchema,
|
|
|
|
ExposeSchema,
|
|
|
|
LightSchema,
|
|
|
|
NotifySchema,
|
|
|
|
SceneSchema,
|
|
|
|
SensorSchema,
|
|
|
|
SwitchSchema,
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
2019-02-14 04:35:12 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2017-09-07 07:11:55 +00:00
|
|
|
CONF_KNX_CONFIG = "config_file"
|
2016-07-10 17:36:54 +00:00
|
|
|
|
2017-09-07 07:11:55 +00:00
|
|
|
CONF_KNX_ROUTING = "routing"
|
|
|
|
CONF_KNX_TUNNELING = "tunneling"
|
|
|
|
CONF_KNX_FIRE_EVENT = "fire_event"
|
|
|
|
CONF_KNX_FIRE_EVENT_FILTER = "fire_event_filter"
|
2017-10-15 21:46:55 +00:00
|
|
|
CONF_KNX_STATE_UPDATER = "state_updater"
|
2019-03-07 21:53:42 +00:00
|
|
|
CONF_KNX_RATE_LIMIT = "rate_limit"
|
2018-02-26 07:44:09 +00:00
|
|
|
CONF_KNX_EXPOSE = "expose"
|
2020-08-26 16:03:03 +00:00
|
|
|
|
2017-09-07 07:11:55 +00:00
|
|
|
SERVICE_KNX_SEND = "send"
|
|
|
|
SERVICE_KNX_ATTR_ADDRESS = "address"
|
|
|
|
SERVICE_KNX_ATTR_PAYLOAD = "payload"
|
2020-08-26 16:03:03 +00:00
|
|
|
SERVICE_KNX_ATTR_TYPE = "type"
|
2017-09-07 07:11:55 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
DOMAIN: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Optional(CONF_KNX_CONFIG): cv.string,
|
2020-08-26 16:03:03 +00:00
|
|
|
vol.Exclusive(
|
|
|
|
CONF_KNX_ROUTING, "connection_type"
|
|
|
|
): ConnectionSchema.ROUTING_SCHEMA,
|
|
|
|
vol.Exclusive(
|
|
|
|
CONF_KNX_TUNNELING, "connection_type"
|
|
|
|
): ConnectionSchema.TUNNELING_SCHEMA,
|
2019-07-31 19:25:30 +00:00
|
|
|
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]
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_KNX_STATE_UPDATER, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_KNX_RATE_LIMIT, default=20): vol.All(
|
|
|
|
vol.Coerce(int), vol.Range(min=1, max=100)
|
|
|
|
),
|
2020-08-26 16:03:03 +00:00
|
|
|
vol.Optional(CONF_KNX_EXPOSE): vol.All(
|
|
|
|
cv.ensure_list, [ExposeSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.cover.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [CoverSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.binary_sensor.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [BinarySensorSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.light.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [LightSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.climate.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [ClimateSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.notify.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [NotifySchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.switch.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [SwitchSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.sensor.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [SensorSchema.SCHEMA]
|
|
|
|
),
|
2020-08-30 18:13:47 +00:00
|
|
|
vol.Optional(SupportedPlatforms.scene.value): vol.All(
|
2020-08-26 16:03:03 +00:00
|
|
|
cv.ensure_list, [SceneSchema.SCHEMA]
|
|
|
|
),
|
2019-07-31 19:25:30 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
|
|
|
|
SERVICE_KNX_SEND_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string,
|
|
|
|
vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any(
|
|
|
|
cv.positive_int, [cv.positive_int]
|
|
|
|
),
|
2020-08-26 16:03:03 +00:00
|
|
|
vol.Optional(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str),
|
2019-07-31 19:25:30 +00:00
|
|
|
}
|
|
|
|
)
|
2017-07-20 05:01:05 +00:00
|
|
|
|
2016-07-10 17:36:54 +00:00
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def async_setup(hass, config):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Set up the KNX component."""
|
2016-07-10 17:36:54 +00:00
|
|
|
try:
|
2017-09-07 07:11:55 +00:00
|
|
|
hass.data[DATA_KNX] = KNXModule(hass, config)
|
2018-02-26 07:44:09 +00:00
|
|
|
hass.data[DATA_KNX].async_create_exposures()
|
2018-02-24 18:24:33 +00:00
|
|
|
await hass.data[DATA_KNX].start()
|
2017-09-07 07:11:55 +00:00
|
|
|
except XKNXException as ex:
|
2018-01-07 21:39:14 +00:00
|
|
|
_LOGGER.warning("Can't connect to KNX interface: %s", ex)
|
|
|
|
hass.components.persistent_notification.async_create(
|
2020-02-28 11:39:29 +00:00
|
|
|
f"Can't connect to KNX interface: <br><b>{ex}</b>", title="KNX"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2017-07-07 05:21:40 +00:00
|
|
|
|
2020-08-30 18:13:47 +00:00
|
|
|
for platform in SupportedPlatforms:
|
|
|
|
if platform.value in config[DOMAIN]:
|
|
|
|
for device_config in config[DOMAIN][platform.value]:
|
|
|
|
create_knx_device(
|
|
|
|
hass, platform, hass.data[DATA_KNX].xknx, device_config
|
2020-08-26 16:03:03 +00:00
|
|
|
)
|
|
|
|
|
2020-08-30 18:13:47 +00:00
|
|
|
# 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:
|
2018-07-23 12:05:38 +00:00
|
|
|
hass.async_create_task(
|
2020-08-30 18:13:47 +00:00
|
|
|
discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config)
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
hass.services.async_register(
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN,
|
|
|
|
SERVICE_KNX_SEND,
|
2017-09-07 07:11:55 +00:00
|
|
|
hass.data[DATA_KNX].service_send_to_knx_bus,
|
2019-07-31 19:25:30 +00:00
|
|
|
schema=SERVICE_KNX_SEND_SCHEMA,
|
|
|
|
)
|
2017-07-12 10:21:15 +00:00
|
|
|
|
2016-07-10 17:36:54 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class KNXModule:
|
2017-09-07 07:11:55 +00:00
|
|
|
"""Representation of KNX Object."""
|
2016-07-10 17:36:54 +00:00
|
|
|
|
|
|
|
def __init__(self, hass, config):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Initialize of KNX module."""
|
2017-09-07 07:11:55 +00:00
|
|
|
self.hass = hass
|
|
|
|
self.config = config
|
2018-01-07 21:39:14 +00:00
|
|
|
self.connected = False
|
2017-09-07 07:11:55 +00:00
|
|
|
self.init_xknx()
|
|
|
|
self.register_callbacks()
|
2018-02-26 07:44:09 +00:00
|
|
|
self.exposures = []
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
def init_xknx(self):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Initialize of KNX object."""
|
2019-07-31 19:25:30 +00:00
|
|
|
self.xknx = XKNX(
|
|
|
|
config=self.config_file(),
|
|
|
|
loop=self.hass.loop,
|
|
|
|
rate_limit=self.config[DOMAIN][CONF_KNX_RATE_LIMIT],
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def start(self):
|
2017-09-07 07:11:55 +00:00
|
|
|
"""Start KNX object. Connect to tunneling or Routing device."""
|
|
|
|
connection_config = self.connection_config()
|
2018-02-24 18:24:33 +00:00
|
|
|
await self.xknx.start(
|
2017-10-15 21:46:55 +00:00
|
|
|
state_updater=self.config[DOMAIN][CONF_KNX_STATE_UPDATER],
|
2019-07-31 19:25:30 +00:00
|
|
|
connection_config=connection_config,
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
|
2018-01-07 21:39:14 +00:00
|
|
|
self.connected = True
|
2017-09-07 07:11:55 +00:00
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def stop(self, event):
|
2017-09-07 07:11:55 +00:00
|
|
|
"""Stop KNX object. Disconnect from tunneling or Routing device."""
|
2018-02-24 18:24:33 +00:00
|
|
|
await self.xknx.stop()
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
def config_file(self):
|
|
|
|
"""Resolve and return the full path of xknx.yaml if configured."""
|
|
|
|
config_file = self.config[DOMAIN].get(CONF_KNX_CONFIG)
|
|
|
|
if not config_file:
|
|
|
|
return None
|
|
|
|
if not config_file.startswith("/"):
|
|
|
|
return self.hass.config.path(config_file)
|
|
|
|
return config_file
|
|
|
|
|
|
|
|
def connection_config(self):
|
|
|
|
"""Return the connection_config."""
|
|
|
|
if CONF_KNX_TUNNELING in self.config[DOMAIN]:
|
|
|
|
return self.connection_config_tunneling()
|
2018-07-23 08:16:05 +00:00
|
|
|
if CONF_KNX_ROUTING in self.config[DOMAIN]:
|
2017-09-07 07:11:55 +00:00
|
|
|
return self.connection_config_routing()
|
2020-08-26 16:03:03 +00:00
|
|
|
# return None to let xknx use config from xknx.yaml connection block if given
|
|
|
|
# otherwise it will use default ConnectionConfig (Automatic)
|
|
|
|
return None
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
def connection_config_routing(self):
|
|
|
|
"""Return the connection_config if routing is configured."""
|
2020-08-26 16:03:03 +00:00
|
|
|
local_ip = self.config[DOMAIN][CONF_KNX_ROUTING].get(
|
|
|
|
ConnectionSchema.CONF_KNX_LOCAL_IP
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
return ConnectionConfig(
|
2019-07-31 19:25:30 +00:00
|
|
|
connection_type=ConnectionType.ROUTING, local_ip=local_ip
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
def connection_config_tunneling(self):
|
|
|
|
"""Return the connection_config if tunneling is configured."""
|
2020-03-31 22:22:20 +00:00
|
|
|
gateway_ip = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_HOST]
|
2019-07-31 19:25:30 +00:00
|
|
|
gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_PORT)
|
2020-08-26 16:03:03 +00:00
|
|
|
local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get(
|
|
|
|
ConnectionSchema.CONF_KNX_LOCAL_IP
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
if gateway_port is None:
|
|
|
|
gateway_port = DEFAULT_MCAST_PORT
|
|
|
|
return ConnectionConfig(
|
2019-07-31 19:25:30 +00:00
|
|
|
connection_type=ConnectionType.TUNNELING,
|
|
|
|
gateway_ip=gateway_ip,
|
|
|
|
gateway_port=gateway_port,
|
|
|
|
local_ip=local_ip,
|
2020-03-31 22:22:20 +00:00
|
|
|
auto_reconnect=True,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
def register_callbacks(self):
|
|
|
|
"""Register callbacks within XKNX object."""
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
CONF_KNX_FIRE_EVENT in self.config[DOMAIN]
|
|
|
|
and self.config[DOMAIN][CONF_KNX_FIRE_EVENT]
|
|
|
|
):
|
|
|
|
address_filters = list(
|
|
|
|
map(AddressFilter, self.config[DOMAIN][CONF_KNX_FIRE_EVENT_FILTER])
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
self.xknx.telegram_queue.register_telegram_received_cb(
|
2019-07-31 19:25:30 +00:00
|
|
|
self.telegram_received_cb, address_filters
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
2018-02-26 07:44:09 +00:00
|
|
|
@callback
|
|
|
|
def async_create_exposures(self):
|
|
|
|
"""Create exposures."""
|
|
|
|
if CONF_KNX_EXPOSE not in self.config[DOMAIN]:
|
|
|
|
return
|
|
|
|
for to_expose in self.config[DOMAIN][CONF_KNX_EXPOSE]:
|
2020-08-26 16:03:03 +00:00
|
|
|
expose_type = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE)
|
2018-02-26 07:44:09 +00:00
|
|
|
entity_id = to_expose.get(CONF_ENTITY_ID)
|
2020-08-26 16:03:03 +00:00
|
|
|
attribute = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE)
|
|
|
|
default = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT)
|
|
|
|
address = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS)
|
2019-07-31 19:25:30 +00:00
|
|
|
if expose_type in ["time", "date", "datetime"]:
|
|
|
|
exposure = KNXExposeTime(self.xknx, expose_type, address)
|
2018-02-26 07:44:09 +00:00
|
|
|
exposure.async_register()
|
|
|
|
self.exposures.append(exposure)
|
|
|
|
else:
|
|
|
|
exposure = KNXExposeSensor(
|
2020-05-13 13:19:00 +00:00
|
|
|
self.hass,
|
|
|
|
self.xknx,
|
|
|
|
expose_type,
|
|
|
|
entity_id,
|
|
|
|
attribute,
|
|
|
|
default,
|
|
|
|
address,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-02-26 07:44:09 +00:00
|
|
|
exposure.async_register()
|
|
|
|
self.exposures.append(exposure)
|
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def telegram_received_cb(self, telegram):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Call invoked after a KNX telegram was received."""
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.bus.async_fire(
|
|
|
|
"knx_event",
|
|
|
|
{"address": str(telegram.group_address), "data": telegram.payload.value},
|
|
|
|
)
|
2017-09-07 07:11:55 +00:00
|
|
|
# False signals XKNX to proceed with processing telegrams.
|
2016-07-10 17:36:54 +00:00
|
|
|
return False
|
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def service_send_to_knx_bus(self, call):
|
2017-09-23 15:15:46 +00:00
|
|
|
"""Service for sending an arbitrary KNX message to the KNX bus."""
|
2017-09-07 07:11:55 +00:00
|
|
|
attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD)
|
|
|
|
attr_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS)
|
2020-08-26 16:03:03 +00:00
|
|
|
attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
def calculate_payload(attr_payload):
|
|
|
|
"""Calculate payload depending on type of attribute."""
|
2020-08-26 16:03:03 +00:00
|
|
|
if attr_type is not None:
|
|
|
|
transcoder = DPTBase.parse_transcoder(attr_type)
|
|
|
|
if transcoder is None:
|
|
|
|
raise ValueError(f"Invalid type for knx.send service: {attr_type}")
|
|
|
|
return DPTArray(transcoder.to_knx(attr_payload))
|
2017-09-07 07:11:55 +00:00
|
|
|
if isinstance(attr_payload, int):
|
|
|
|
return DPTBinary(attr_payload)
|
|
|
|
return DPTArray(attr_payload)
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2017-09-07 07:11:55 +00:00
|
|
|
payload = calculate_payload(attr_payload)
|
2018-02-15 06:06:36 +00:00
|
|
|
address = GroupAddress(attr_address)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
telegram = Telegram()
|
|
|
|
telegram.payload = payload
|
|
|
|
telegram.group_address = address
|
2018-02-24 18:24:33 +00:00
|
|
|
await self.xknx.telegrams.put(telegram)
|
2017-09-07 07:11:55 +00:00
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class KNXExposeTime:
|
2018-02-26 07:44:09 +00:00
|
|
|
"""Object to Expose Time/Date object to KNX bus."""
|
|
|
|
|
|
|
|
def __init__(self, xknx, expose_type, address):
|
|
|
|
"""Initialize of Expose class."""
|
|
|
|
self.xknx = xknx
|
|
|
|
self.type = expose_type
|
|
|
|
self.address = address
|
|
|
|
self.device = None
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_register(self):
|
|
|
|
"""Register listener."""
|
|
|
|
broadcast_type_string = self.type.upper()
|
2020-08-26 16:03:03 +00:00
|
|
|
broadcast_type = broadcast_type_string
|
2018-02-26 07:44:09 +00:00
|
|
|
self.device = DateTime(
|
2019-07-31 19:25:30 +00:00
|
|
|
self.xknx, "Time", broadcast_type=broadcast_type, group_address=self.address
|
|
|
|
)
|
2018-02-26 07:44:09 +00:00
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class KNXExposeSensor:
|
2020-01-05 12:09:17 +00:00
|
|
|
"""Object to Expose Home Assistant entity to KNX bus."""
|
2018-02-26 07:44:09 +00:00
|
|
|
|
2020-05-13 13:19:00 +00:00
|
|
|
def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address):
|
2018-02-26 07:44:09 +00:00
|
|
|
"""Initialize of Expose class."""
|
|
|
|
self.hass = hass
|
|
|
|
self.xknx = xknx
|
|
|
|
self.type = expose_type
|
|
|
|
self.entity_id = entity_id
|
2020-05-13 13:19:00 +00:00
|
|
|
self.expose_attribute = attribute
|
|
|
|
self.expose_default = default
|
2018-02-26 07:44:09 +00:00
|
|
|
self.address = address
|
|
|
|
self.device = None
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_register(self):
|
|
|
|
"""Register listener."""
|
2020-05-13 13:19:00 +00:00
|
|
|
if self.expose_attribute is not None:
|
|
|
|
_name = self.entity_id + "__" + self.expose_attribute
|
|
|
|
else:
|
|
|
|
_name = self.entity_id
|
2018-02-26 07:44:09 +00:00
|
|
|
self.device = ExposeSensor(
|
2020-08-27 11:56:20 +00:00
|
|
|
self.xknx,
|
|
|
|
name=_name,
|
|
|
|
group_address=self.address,
|
|
|
|
value_type=self.type,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2020-07-15 06:37:25 +00:00
|
|
|
async_track_state_change_event(
|
|
|
|
self.hass, [self.entity_id], self._async_entity_changed
|
|
|
|
)
|
2018-02-26 07:44:09 +00:00
|
|
|
|
2020-07-15 06:37:25 +00:00
|
|
|
async def _async_entity_changed(self, event):
|
2018-08-24 08:28:43 +00:00
|
|
|
"""Handle entity change."""
|
2020-07-15 06:37:25 +00:00
|
|
|
new_state = event.data.get("new_state")
|
2018-02-26 07:44:09 +00:00
|
|
|
if new_state is None:
|
|
|
|
return
|
2020-05-13 13:19:00 +00:00
|
|
|
if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
2019-07-11 20:01:37 +00:00
|
|
|
return
|
|
|
|
|
2020-05-13 13:19:00 +00:00
|
|
|
if self.expose_attribute is not None:
|
|
|
|
new_attribute = new_state.attributes.get(self.expose_attribute)
|
2020-07-15 06:37:25 +00:00
|
|
|
old_state = event.data.get("old_state")
|
|
|
|
|
2020-05-13 13:19:00 +00:00
|
|
|
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)
|
2019-07-11 20:01:37 +00:00
|
|
|
else:
|
2020-05-13 13:19:00 +00:00
|
|
|
await self._async_set_knx_value(new_state.state)
|
|
|
|
|
|
|
|
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)
|