diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py
index 5a2f29e6247..f9f78f195bb 100644
--- a/homeassistant/components/knx/__init__.py
+++ b/homeassistant/components/knx/__init__.py
@@ -6,7 +6,12 @@ from xknx import XKNX
from xknx.devices import DateTime, ExposeSensor
from xknx.dpt import DPTArray, DPTBase, DPTBinary
from xknx.exceptions import XKNXException
-from xknx.io import DEFAULT_MCAST_PORT, ConnectionConfig, ConnectionType
+from xknx.io import (
+ DEFAULT_MCAST_GRP,
+ DEFAULT_MCAST_PORT,
+ ConnectionConfig,
+ ConnectionType,
+)
from xknx.telegram import AddressFilter, GroupAddress, Telegram
from homeassistant.const import (
@@ -48,6 +53,9 @@ CONF_KNX_ROUTING = "routing"
CONF_KNX_TUNNELING = "tunneling"
CONF_KNX_FIRE_EVENT = "fire_event"
CONF_KNX_FIRE_EVENT_FILTER = "fire_event_filter"
+CONF_KNX_INDIVIDUAL_ADDRESS = "individual_address"
+CONF_KNX_MCAST_GRP = "multicast_group"
+CONF_KNX_MCAST_PORT = "multicast_port"
CONF_KNX_STATE_UPDATER = "state_updater"
CONF_KNX_RATE_LIMIT = "rate_limit"
CONF_KNX_EXPOSE = "expose"
@@ -72,6 +80,11 @@ CONFIG_SCHEMA = vol.Schema(
vol.Inclusive(CONF_KNX_FIRE_EVENT_FILTER, "fire_ev"): vol.All(
cv.ensure_list, [cv.string]
),
+ vol.Optional(
+ CONF_KNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS
+ ): cv.string,
+ vol.Optional(CONF_KNX_MCAST_GRP, default=DEFAULT_MCAST_GRP): cv.string,
+ vol.Optional(CONF_KNX_MCAST_PORT, default=DEFAULT_MCAST_PORT): cv.port,
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)
@@ -130,17 +143,15 @@ async def async_setup(hass, config):
hass.data[DATA_KNX].async_create_exposures()
await hass.data[DATA_KNX].start()
except XKNXException as ex:
- _LOGGER.warning("Can't connect to KNX interface: %s", ex)
+ _LOGGER.warning("Could not connect to KNX interface: %s", ex)
hass.components.persistent_notification.async_create(
- f"Can't connect to KNX interface:
{ex}", title="KNX"
+ f"Could not connect to KNX interface:
{ex}", title="KNX"
)
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
- )
+ create_knx_device(platform, hass.data[DATA_KNX].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
for platform in SupportedPlatforms:
@@ -181,7 +192,10 @@ class KNXModule:
self.xknx = XKNX(
config=self.config_file(),
loop=self.hass.loop,
+ own_address=self.config[DOMAIN][CONF_KNX_INDIVIDUAL_ADDRESS],
rate_limit=self.config[DOMAIN][CONF_KNX_RATE_LIMIT],
+ multicast_group=self.config[DOMAIN][CONF_KNX_MCAST_GRP],
+ multicast_port=self.config[DOMAIN][CONF_KNX_MCAST_PORT],
)
async def start(self):
@@ -229,12 +243,10 @@ class KNXModule:
def connection_config_tunneling(self):
"""Return the connection_config if tunneling is configured."""
gateway_ip = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_HOST]
- gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_PORT)
+ gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_PORT]
local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get(
ConnectionSchema.CONF_KNX_LOCAL_IP
)
- if gateway_port is None:
- gateway_port = DEFAULT_MCAST_PORT
return ConnectionConfig(
connection_type=ConnectionType.TUNNELING,
gateway_ip=gateway_ip,
@@ -267,7 +279,7 @@ class KNXModule:
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)
- if expose_type in ["time", "date", "datetime"]:
+ if expose_type.lower() in ["time", "date", "datetime"]:
exposure = KNXExposeTime(self.xknx, expose_type, address)
exposure.async_register()
self.exposures.append(exposure)
@@ -313,29 +325,29 @@ class KNXModule:
payload = calculate_payload(attr_payload)
address = GroupAddress(attr_address)
- telegram = Telegram()
- telegram.payload = payload
- telegram.group_address = address
+ telegram = Telegram(group_address=address, payload=payload)
await self.xknx.telegrams.put(telegram)
class KNXExposeTime:
"""Object to Expose Time/Date object to KNX bus."""
- def __init__(self, xknx, expose_type, address):
+ def __init__(self, xknx: XKNX, expose_type: str, address: str):
"""Initialize of Expose class."""
self.xknx = xknx
- self.type = expose_type
+ self.expose_type = expose_type
self.address = address
self.device = None
@callback
def async_register(self):
"""Register listener."""
- broadcast_type_string = self.type.upper()
- broadcast_type = broadcast_type_string
self.device = DateTime(
- self.xknx, "Time", broadcast_type=broadcast_type, group_address=self.address
+ self.xknx,
+ name=self.expose_type.capitalize(),
+ broadcast_type=self.expose_type.upper(),
+ localtime=True,
+ group_address=self.address,
)
diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py
index f3b7e881134..24fbe472ae3 100644
--- a/homeassistant/components/knx/binary_sensor.py
+++ b/homeassistant/components/knx/binary_sensor.py
@@ -1,10 +1,12 @@
"""Support for KNX/IP binary sensors."""
+from typing import Any, Dict, Optional
+
from xknx.devices import BinarySensor as XknxBinarySensor
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import callback
-from . import DATA_KNX
+from .const import ATTR_COUNTER, DATA_KNX
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
@@ -27,7 +29,7 @@ class KNXBinarySensor(BinarySensorEntity):
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
- async def after_update_callback(device):
+ async def after_update_callback(device: XknxBinarySensor):
"""Call after device was updated."""
self.async_write_ha_state()
@@ -65,3 +67,8 @@ class KNXBinarySensor(BinarySensorEntity):
def is_on(self):
"""Return true if the binary sensor is on."""
return self.device.is_on()
+
+ @property
+ def device_state_attributes(self) -> Optional[Dict[str, Any]]:
+ """Return device specific state attributes."""
+ return {ATTR_COUNTER: self.device.counter}
diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py
index a81fc526415..427bdb0bd0b 100644
--- a/homeassistant/components/knx/const.py
+++ b/homeassistant/components/knx/const.py
@@ -60,3 +60,5 @@ PRESET_MODES = {
"Standby": PRESET_AWAY,
"Comfort": PRESET_COMFORT,
}
+
+ATTR_COUNTER = "counter"
diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py
index 42c4dd675f5..3334e49ce38 100644
--- a/homeassistant/components/knx/factory.py
+++ b/homeassistant/components/knx/factory.py
@@ -1,7 +1,6 @@
"""Factory function to initialize KNX devices from config."""
from xknx import XKNX
from xknx.devices import (
- ActionCallback as XknxActionCallback,
BinarySensor as XknxBinarySensor,
Climate as XknxClimate,
ClimateMode as XknxClimateMode,
@@ -16,11 +15,9 @@ from xknx.devices import (
)
from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE
-from homeassistant.core import HomeAssistant
-from homeassistant.helpers.script import Script
from homeassistant.helpers.typing import ConfigType
-from .const import DOMAIN, ColorTempModes, SupportedPlatforms
+from .const import ColorTempModes, SupportedPlatforms
from .schema import (
BinarySensorSchema,
ClimateSchema,
@@ -34,7 +31,6 @@ from .schema import (
def create_knx_device(
- hass: HomeAssistant,
platform: SupportedPlatforms,
knx_module: XKNX,
config: ConfigType,
@@ -62,7 +58,7 @@ def create_knx_device(
return _create_scene(knx_module, config)
if platform is SupportedPlatforms.binary_sensor:
- return _create_binary_sensor(hass, knx_module, config)
+ return _create_binary_sensor(knx_module, config)
if platform is SupportedPlatforms.weather:
return _create_weather(knx_module, config)
@@ -239,24 +235,9 @@ def _create_scene(knx_module: XKNX, config: ConfigType) -> XknxScene:
)
-def _create_binary_sensor(
- hass: HomeAssistant, knx_module: XKNX, config: ConfigType
-) -> XknxBinarySensor:
+def _create_binary_sensor(knx_module: XKNX, config: ConfigType) -> XknxBinarySensor:
"""Return a KNX binary sensor to be used within XKNX."""
device_name = config[CONF_NAME]
- actions = []
- automations = config.get(BinarySensorSchema.CONF_AUTOMATION)
- if automations is not None:
- for automation in automations:
- counter = automation[BinarySensorSchema.CONF_COUNTER]
- hook = automation[BinarySensorSchema.CONF_HOOK]
- action = automation[BinarySensorSchema.CONF_ACTION]
- script_name = f"{device_name} turn ON script"
- script = Script(hass, action, script_name, DOMAIN)
- action = XknxActionCallback(
- knx_module, script.async_run, hook=hook, counter=counter
- )
- actions.append(action)
return XknxBinarySensor(
knx_module,
@@ -265,8 +246,8 @@ def _create_binary_sensor(
sync_state=config[BinarySensorSchema.CONF_SYNC_STATE],
device_class=config.get(CONF_DEVICE_CLASS),
ignore_internal_state=config[BinarySensorSchema.CONF_IGNORE_INTERNAL_STATE],
+ context_timeout=config[BinarySensorSchema.CONF_CONTEXT_TIMEOUT],
reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER),
- actions=actions,
)
@@ -287,6 +268,9 @@ def _create_weather(knx_module: XKNX, config: ConfigType) -> XknxWeather:
group_address_brightness_west=config.get(
WeatherSchema.CONF_KNX_BRIGHTNESS_WEST_ADDRESS
),
+ group_address_brightness_north=config.get(
+ WeatherSchema.CONF_KNX_BRIGHTNESS_NORTH_ADDRESS
+ ),
group_address_wind_speed=config.get(WeatherSchema.CONF_KNX_WIND_SPEED_ADDRESS),
group_address_rain_alarm=config.get(WeatherSchema.CONF_KNX_RAIN_ALARM_ADDRESS),
group_address_frost_alarm=config.get(
diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json
index 8986d85b8b6..af9f99677f3 100644
--- a/homeassistant/components/knx/manifest.json
+++ b/homeassistant/components/knx/manifest.json
@@ -2,6 +2,6 @@
"domain": "knx",
"name": "KNX",
"documentation": "https://www.home-assistant.io/integrations/knx",
- "requirements": ["xknx==0.13.0"],
+ "requirements": ["xknx==0.14.2"],
"codeowners": ["@Julius2342", "@farmio", "@marvin-w"]
}
diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py
index a436f2dcdc8..84a54536db5 100644
--- a/homeassistant/components/knx/schema.py
+++ b/homeassistant/components/knx/schema.py
@@ -1,6 +1,7 @@
"""Voluptuous schemas for the KNX integration."""
import voluptuous as vol
from xknx.devices.climate import SetpointShiftMode
+from xknx.io import DEFAULT_MCAST_PORT
from homeassistant.const import (
CONF_ADDRESS,
@@ -29,9 +30,9 @@ class ConnectionSchema:
TUNNELING_SCHEMA = vol.Schema(
{
+ vol.Optional(CONF_PORT, default=DEFAULT_MCAST_PORT): cv.port,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_KNX_LOCAL_IP): cv.string,
- vol.Optional(CONF_PORT): cv.port,
}
)
@@ -84,27 +85,14 @@ class BinarySensorSchema:
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
CONF_SYNC_STATE = CONF_SYNC_STATE
CONF_IGNORE_INTERNAL_STATE = "ignore_internal_state"
- CONF_AUTOMATION = "automation"
- CONF_HOOK = "hook"
- CONF_DEFAULT_HOOK = "on"
- CONF_COUNTER = "counter"
- CONF_DEFAULT_COUNTER = 1
- CONF_ACTION = "action"
+ CONF_CONTEXT_TIMEOUT = "context_timeout"
CONF_RESET_AFTER = "reset_after"
DEFAULT_NAME = "KNX Binary Sensor"
- 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): cv.SCRIPT_SCHEMA,
- }
- )
-
- AUTOMATIONS_SCHEMA = vol.All(cv.ensure_list, [AUTOMATION_SCHEMA])
SCHEMA = vol.All(
cv.deprecated("significant_bit"),
+ cv.deprecated("automation"),
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -113,11 +101,13 @@ class BinarySensorSchema:
cv.boolean,
cv.string,
),
- vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean,
+ vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=True): cv.boolean,
+ vol.Optional(CONF_CONTEXT_TIMEOUT, default=1.0): vol.All(
+ vol.Coerce(float), vol.Range(min=0, max=10)
+ ),
vol.Required(CONF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_RESET_AFTER): cv.positive_int,
- vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
}
),
)
@@ -350,6 +340,7 @@ class WeatherSchema:
CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS = "address_brightness_south"
CONF_KNX_BRIGHTNESS_EAST_ADDRESS = "address_brightness_east"
CONF_KNX_BRIGHTNESS_WEST_ADDRESS = "address_brightness_west"
+ CONF_KNX_BRIGHTNESS_NORTH_ADDRESS = "address_brightness_north"
CONF_KNX_WIND_SPEED_ADDRESS = "address_wind_speed"
CONF_KNX_RAIN_ALARM_ADDRESS = "address_rain_alarm"
CONF_KNX_FROST_ALARM_ADDRESS = "address_frost_alarm"
@@ -374,6 +365,7 @@ class WeatherSchema:
vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): cv.string,
vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): cv.string,
vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): cv.string,
+ vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): cv.string,
vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): cv.string,
vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): cv.string,
vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): cv.string,
diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py
index 97500ef8194..09dc1a305c6 100644
--- a/homeassistant/components/knx/weather.py
+++ b/homeassistant/components/knx/weather.py
@@ -1,4 +1,5 @@
"""Support for KNX/IP weather station."""
+
from xknx.devices import Weather as XknxWeather
from homeassistant.components.weather import WeatherEntity
diff --git a/requirements_all.txt b/requirements_all.txt
index 73e33aa80ab..6146c531bf2 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2265,7 +2265,7 @@ xboxapi==2.0.1
xfinity-gateway==0.0.4
# homeassistant.components.knx
-xknx==0.13.0
+xknx==0.14.2
# homeassistant.components.bluesound
# homeassistant.components.rest