From 45288431f91e8d304283af3f3857fb0523e5cbca Mon Sep 17 00:00:00 2001 From: Marvin Wichmann Date: Sun, 20 Sep 2020 23:40:36 +0200 Subject: [PATCH] Update xknx to 0.14.2 (#40304) * Updates xknx to 0.14.0 * Review: Explicity add state attributes to weather device * Review: Remove state attributes from weather device * Review: Add `counter` as a state attribute to binary_sensors --- homeassistant/components/knx/__init__.py | 48 ++++++++++++------- homeassistant/components/knx/binary_sensor.py | 11 ++++- homeassistant/components/knx/const.py | 2 + homeassistant/components/knx/factory.py | 30 +++--------- homeassistant/components/knx/manifest.json | 2 +- homeassistant/components/knx/schema.py | 28 ++++------- homeassistant/components/knx/weather.py | 1 + requirements_all.txt | 2 +- 8 files changed, 61 insertions(+), 63 deletions(-) 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