diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index a62e95f1def..35feb09dc1d 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -41,3 +41,13 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity): def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Return device specific state attributes.""" return {ATTR_COUNTER: self._device.counter} + + @property + def force_update(self) -> bool: + """ + Return True if state updates should be forced. + + If True, a state change will be triggered anytime the state property is + updated, not just when the value changes. + """ + return self._device.ignore_internal_state diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 1960627a8d6..070f635cc48 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -2,7 +2,7 @@ from typing import List, Optional from xknx.devices import Climate as XknxClimate -from xknx.dpt import HVACOperationMode +from xknx.dpt import HVACControllerMode, HVACOperationMode from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -14,11 +14,11 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from .const import DOMAIN, OPERATION_MODES, PRESET_MODES +from .const import CONTROLLER_MODES, DOMAIN, PRESET_MODES from .knx_entity import KnxEntity -OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items()) -PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items()) +CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()} +PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()} async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -92,27 +92,27 @@ class KNXClimate(KnxEntity, ClimateEntity): """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF - if self._device.mode.supports_operation_mode: - return OPERATION_MODES.get( - self._device.mode.operation_mode.value, HVAC_MODE_HEAT + if self._device.mode.supports_controller_mode: + return CONTROLLER_MODES.get( + self._device.mode.controller_mode.value, HVAC_MODE_HEAT ) # default to "heat" return HVAC_MODE_HEAT @property def hvac_modes(self) -> Optional[List[str]]: - """Return the list of available operation modes.""" - _operations = [ - OPERATION_MODES.get(operation_mode.value) - for operation_mode in self._device.mode.operation_modes + """Return the list of available operation/controller modes.""" + _controller_modes = [ + CONTROLLER_MODES.get(controller_mode.value) + for controller_mode in self._device.mode.controller_modes ] if self._device.supports_on_off: - if not _operations: - _operations.append(HVAC_MODE_HEAT) - _operations.append(HVAC_MODE_OFF) + if not _controller_modes: + _controller_modes.append(HVAC_MODE_HEAT) + _controller_modes.append(HVAC_MODE_OFF) - _modes = list(set(filter(None, _operations))) + _modes = list(set(filter(None, _controller_modes))) # default to ["heat"] return _modes if _modes else [HVAC_MODE_HEAT] @@ -123,11 +123,11 @@ class KNXClimate(KnxEntity, ClimateEntity): else: if self._device.supports_on_off and not self._device.is_on: await self._device.turn_on() - if self._device.mode.supports_operation_mode: - knx_operation_mode = HVACOperationMode( - OPERATION_MODES_INV.get(hvac_mode) + if self._device.mode.supports_controller_mode: + knx_controller_mode = HVACControllerMode( + CONTROLLER_MODES_INV.get(hvac_mode) ) - await self._device.mode.set_operation_mode(knx_operation_mode) + await self._device.mode.set_controller_mode(knx_controller_mode) self.async_write_ha_state() @property diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 8b0dd90393b..e434aed395d 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -11,13 +11,16 @@ from homeassistant.components.climate.const import ( PRESET_AWAY, PRESET_COMFORT, PRESET_ECO, + PRESET_NONE, PRESET_SLEEP, ) DOMAIN = "knx" +CONF_INVERT = "invert" CONF_STATE_ADDRESS = "state_address" CONF_SYNC_STATE = "sync_state" +CONF_RESET_AFTER = "reset_after" class ColorTempModes(Enum): @@ -41,8 +44,8 @@ class SupportedPlatforms(Enum): weather = "weather" -# Map KNX operation modes to HA modes. This list might not be complete. -OPERATION_MODES = { +# Map KNX controller modes to HA modes. This list might not be complete. +CONTROLLER_MODES = { # Map DPT 20.105 HVAC control modes "Auto": HVAC_MODE_AUTO, "Heat": HVAC_MODE_HEAT, @@ -54,6 +57,7 @@ OPERATION_MODES = { PRESET_MODES = { # Map DPT 20.102 HVAC operating modes to HA presets + "Auto": PRESET_NONE, "Frost Protection": PRESET_ECO, "Night": PRESET_SLEEP, "Standby": PRESET_AWAY, diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 3334e49ce38..6da2b73cd6a 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -164,6 +164,7 @@ def _create_climate(knx_module: XKNX, config: ConfigType) -> XknxClimate: ClimateSchema.CONF_HEAT_COOL_STATE_ADDRESS ), operation_modes=config.get(ClimateSchema.CONF_OPERATION_MODES), + controller_modes=config.get(ClimateSchema.CONF_CONTROLLER_MODES), ) return XknxClimate( @@ -202,6 +203,7 @@ def _create_switch(knx_module: XKNX, config: ConfigType) -> XknxSwitch: name=config[CONF_NAME], group_address=config[CONF_ADDRESS], group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS), + invert=config.get(SwitchSchema.CONF_INVERT), ) @@ -212,6 +214,7 @@ def _create_sensor(knx_module: XKNX, config: ConfigType) -> XknxSensor: name=config[CONF_NAME], group_address_state=config[SensorSchema.CONF_STATE_ADDRESS], sync_state=config[SensorSchema.CONF_SYNC_STATE], + always_callback=config[SensorSchema.CONF_ALWAYS_CALLBACK], value_type=config[CONF_TYPE], ) @@ -243,10 +246,11 @@ def _create_binary_sensor(knx_module: XKNX, config: ConfigType) -> XknxBinarySen knx_module, name=device_name, group_address_state=config[BinarySensorSchema.CONF_STATE_ADDRESS], + invert=config.get(BinarySensorSchema.CONF_INVERT), 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], + context_timeout=config.get(BinarySensorSchema.CONF_CONTEXT_TIMEOUT), reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER), ) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 2d387f0653d..4055048fd2d 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.15.0"], + "requirements": ["xknx==0.15.3"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 84a54536db5..cbf06925163 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -15,9 +15,11 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from .const import ( + CONF_INVERT, + CONF_RESET_AFTER, CONF_STATE_ADDRESS, CONF_SYNC_STATE, - OPERATION_MODES, + CONTROLLER_MODES, PRESET_MODES, ColorTempModes, ) @@ -84,9 +86,10 @@ class BinarySensorSchema: CONF_STATE_ADDRESS = CONF_STATE_ADDRESS CONF_SYNC_STATE = CONF_SYNC_STATE + CONF_INVERT = CONF_INVERT CONF_IGNORE_INTERNAL_STATE = "ignore_internal_state" CONF_CONTEXT_TIMEOUT = "context_timeout" - CONF_RESET_AFTER = "reset_after" + CONF_RESET_AFTER = CONF_RESET_AFTER DEFAULT_NAME = "KNX Binary Sensor" @@ -101,12 +104,13 @@ class BinarySensorSchema: cv.boolean, cv.string, ), - vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=True): cv.boolean, - vol.Optional(CONF_CONTEXT_TIMEOUT, default=1.0): vol.All( + vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean, + vol.Required(CONF_STATE_ADDRESS): cv.string, + vol.Optional(CONF_CONTEXT_TIMEOUT): 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_INVERT): cv.boolean, vol.Optional(CONF_RESET_AFTER): cv.positive_int, } ), @@ -187,6 +191,7 @@ class ClimateSchema: CONF_OPERATION_MODE_COMFORT_ADDRESS = "operation_mode_comfort_address" CONF_OPERATION_MODE_STANDBY_ADDRESS = "operation_mode_standby_address" CONF_OPERATION_MODES = "operation_modes" + CONF_CONTROLLER_MODES = "controller_modes" CONF_ON_OFF_ADDRESS = "on_off_address" CONF_ON_OFF_STATE_ADDRESS = "on_off_state_address" CONF_ON_OFF_INVERT = "on_off_invert" @@ -240,7 +245,10 @@ class ClimateSchema: CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT ): cv.boolean, vol.Optional(CONF_OPERATION_MODES): vol.All( - cv.ensure_list, [vol.In({**OPERATION_MODES, **PRESET_MODES})] + cv.ensure_list, [vol.In({**PRESET_MODES})] + ), + vol.Optional(CONF_CONTROLLER_MODES): vol.All( + cv.ensure_list, [vol.In({**CONTROLLER_MODES})] ), vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), @@ -252,6 +260,7 @@ class ClimateSchema: class SwitchSchema: """Voluptuous schema for KNX switches.""" + CONF_INVERT = CONF_INVERT CONF_STATE_ADDRESS = CONF_STATE_ADDRESS DEFAULT_NAME = "KNX Switch" @@ -260,6 +269,7 @@ class SwitchSchema: vol.Required(CONF_ADDRESS): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_STATE_ADDRESS): cv.string, + vol.Optional(CONF_INVERT): cv.boolean, } ) @@ -299,6 +309,7 @@ class NotifySchema: class SensorSchema: """Voluptuous schema for KNX sensors.""" + CONF_ALWAYS_CALLBACK = "always_callback" CONF_STATE_ADDRESS = CONF_STATE_ADDRESS CONF_SYNC_STATE = CONF_SYNC_STATE DEFAULT_NAME = "KNX Sensor" @@ -311,6 +322,7 @@ class SensorSchema: cv.boolean, cv.string, ), + vol.Optional(CONF_ALWAYS_CALLBACK, default=False): cv.boolean, vol.Required(CONF_STATE_ADDRESS): cv.string, vol.Required(CONF_TYPE): vol.Any(int, float, str), } diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index fc2cbced8bb..dc9ffcb61b7 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -41,3 +41,13 @@ class KNXSensor(KnxEntity, Entity): if device_class in DEVICE_CLASSES: return device_class return None + + @property + def force_update(self) -> bool: + """ + Return True if state updates should be forced. + + If True, a state change will be triggered anytime the state property is + updated, not just when the value changes. + """ + return self._device.always_callback diff --git a/requirements_all.txt b/requirements_all.txt index 60981d79764..990c6ed969c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2304,7 +2304,7 @@ xboxapi==2.0.1 xfinity-gateway==0.0.4 # homeassistant.components.knx -xknx==0.15.0 +xknx==0.15.3 # homeassistant.components.bluesound # homeassistant.components.rest