Cleanup KNX integration (#52168)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
pull/52006/head^2
Matthias Alphart 2021-06-26 14:30:36 +02:00 committed by GitHub
parent b7c15d4474
commit 5687ced7b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 115 additions and 210 deletions

View File

@ -298,7 +298,6 @@ class KNXModule:
return self.connection_config_tunneling()
if CONF_KNX_ROUTING in self.config[DOMAIN]:
return self.connection_config_routing()
# config from xknx.yaml always has priority later on
return ConnectionConfig(auto_reconnect=True)
def connection_config_routing(self) -> ConnectionConfig:
@ -379,14 +378,16 @@ class KNXModule:
"Service event_register could not remove event for '%s'",
str(group_address),
)
else:
for group_address in group_addresses:
if group_address not in self._knx_event_callback.group_addresses:
self._knx_event_callback.group_addresses.append(group_address)
_LOGGER.debug(
"Service event_register registered event for '%s'",
str(group_address),
)
return
for group_address in group_addresses:
if group_address in self._knx_event_callback.group_addresses:
continue
self._knx_event_callback.group_addresses.append(group_address)
_LOGGER.debug(
"Service event_register registered event for '%s'",
str(group_address),
)
async def service_exposure_register_modify(self, call: ServiceCall) -> None:
"""Service for adding or removing an exposure to KNX bus."""
@ -405,7 +406,6 @@ class KNXModule:
if group_address in self.service_exposures:
replaced_exposure = self.service_exposures.pop(group_address)
assert replaced_exposure.device is not None
_LOGGER.warning(
"Service exposure_register replacing already registered exposure for '%s' - %s",
group_address,

View File

@ -31,19 +31,18 @@ async def async_setup_platform(
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXBinarySensor(xknx, entity_config))
async_add_entities(entities)
async_add_entities(
KNXBinarySensor(xknx, entity_config) for entity_config in platform_config
)
class KNXBinarySensor(KnxEntity, BinarySensorEntity):
"""Representation of a KNX binary sensor."""
_device: XknxBinarySensor
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of KNX binary sensor."""
self._device: XknxBinarySensor
super().__init__(
device=XknxBinarySensor(
xknx,
@ -58,13 +57,9 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity):
reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER),
)
)
self._device_class: str | None = config.get(CONF_DEVICE_CLASS)
self._unique_id = f"{self._device.remote_value.group_address_state}"
@property
def device_class(self) -> str | None:
"""Return the class of this sensor."""
return self._device_class
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
self._attr_force_update = self._device.ignore_internal_state
self._attr_unique_id = str(self._device.remote_value.group_address_state)
@property
def is_on(self) -> bool:
@ -84,13 +79,3 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity):
dt.as_utc(self._device.last_telegram.timestamp)
)
return attr
@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

View File

@ -46,11 +46,9 @@ async def async_setup_platform(
xknx: XKNX = hass.data[DOMAIN].xknx
_async_migrate_unique_id(hass, platform_config)
entities = []
for entity_config in platform_config:
entities.append(KNXClimate(xknx, entity_config))
async_add_entities(entities)
async_add_entities(
KNXClimate(xknx, entity_config) for entity_config in platform_config
)
@callback
@ -168,22 +166,20 @@ def _create_climate(xknx: XKNX, config: ConfigType) -> XknxClimate:
class KNXClimate(KnxEntity, ClimateEntity):
"""Representation of a KNX climate device."""
_device: XknxClimate
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of a KNX climate device."""
self._device: XknxClimate
super().__init__(_create_climate(xknx, config))
self._unique_id = (
self._attr_target_temperature_step = self._device.temperature_step
self._attr_unique_id = (
f"{self._device.temperature.group_address_state}_"
f"{self._device.target_temperature.group_address_state}_"
f"{self._device.target_temperature.group_address}_"
f"{self._device._setpoint_shift.group_address}" # pylint: disable=protected-access
)
self._unit_of_measurement = TEMP_CELSIUS
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
async def async_update(self) -> None:
"""Request a state update from KNX bus."""
@ -191,21 +187,11 @@ class KNXClimate(KnxEntity, ClimateEntity):
if self._device.mode is not None:
await self._device.mode.sync()
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._device.temperature.value
@property
def target_temperature_step(self) -> float:
"""Return the supported step of target temperature."""
return self._device.temperature_step
@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""

View File

@ -37,8 +37,8 @@ CONF_STATE_ADDRESS: Final = "state_address"
CONF_SYNC_STATE: Final = "sync_state"
ATTR_COUNTER: Final = "counter"
ATTR_SOURCE: Final = "source"
ATTR_LAST_KNX_UPDATE: Final = "last_knx_update"
ATTR_SOURCE: Final = "source"
class ColorTempModes(Enum):

View File

@ -44,15 +44,12 @@ async def async_setup_platform(
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
_async_migrate_unique_id(hass, platform_config)
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXCover(xknx, entity_config))
async_add_entities(entities)
_async_migrate_unique_id(hass, platform_config)
async_add_entities(
KNXCover(xknx, entity_config) for entity_config in platform_config
)
@callback
@ -85,9 +82,10 @@ def _async_migrate_unique_id(
class KNXCover(KnxEntity, CoverEntity):
"""Representation of a KNX cover."""
_device: XknxCover
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize the cover."""
self._device: XknxCover
super().__init__(
device=XknxCover(
xknx,
@ -109,12 +107,15 @@ class KNXCover(KnxEntity, CoverEntity):
invert_angle=config[CoverSchema.CONF_INVERT_ANGLE],
)
)
self._device_class: str | None = config.get(CONF_DEVICE_CLASS)
self._unique_id = (
self._unsubscribe_auto_updater: Callable[[], None] | None = None
self._attr_device_class = config.get(CONF_DEVICE_CLASS) or (
DEVICE_CLASS_BLIND if self._device.supports_angle else None
)
self._attr_unique_id = (
f"{self._device.updown.group_address}_"
f"{self._device.position_target.group_address}"
)
self._unsubscribe_auto_updater: Callable[[], None] | None = None
@callback
async def after_update_callback(self, device: XknxDevice) -> None:
@ -123,15 +124,6 @@ class KNXCover(KnxEntity, CoverEntity):
if self._device.is_traveling():
self.start_auto_updater()
@property
def device_class(self) -> str | None:
"""Return the class of this device, from component DEVICE_CLASSES."""
if self._device_class:
return self._device_class
if self._device.supports_angle:
return DEVICE_CLASS_BLIND
return None
@property
def supported_features(self) -> int:
"""Flag supported features."""

View File

@ -34,23 +34,19 @@ async def async_setup_platform(
"""Set up fans for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXFan(xknx, entity_config))
async_add_entities(entities)
async_add_entities(KNXFan(xknx, entity_config) for entity_config in platform_config)
class KNXFan(KnxEntity, FanEntity):
"""Representation of a KNX fan."""
_device: XknxFan
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of KNX fan."""
self._device: XknxFan
max_step = config.get(FanSchema.CONF_MAX_STEP)
super().__init__(
device=XknxFan(
@ -67,10 +63,16 @@ class KNXFan(KnxEntity, FanEntity):
max_step=max_step,
)
)
self._unique_id = f"{self._device.speed.group_address}"
# FanSpeedMode.STEP if max_step is set
self._step_range: tuple[int, int] | None = (1, max_step) if max_step else None
self._attr_supported_features = (
SUPPORT_SET_SPEED | SUPPORT_OSCILLATE
if self._device.supports_oscillation
else SUPPORT_SET_SPEED
)
self._attr_unique_id = str(self._device.speed.group_address)
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed of the fan, as a percentage."""
if self._step_range:
@ -79,16 +81,6 @@ class KNXFan(KnxEntity, FanEntity):
else:
await self._device.set_speed(percentage)
@property
def supported_features(self) -> int:
"""Flag supported features."""
flags = SUPPORT_SET_SPEED
if self._device.supports_oscillation:
flags |= SUPPORT_OSCILLATE
return flags
@property
def percentage(self) -> int | None:
"""Return the current speed as a percentage."""

View File

@ -14,10 +14,11 @@ from .const import DOMAIN
class KnxEntity(Entity):
"""Representation of a KNX entity."""
_attr_should_poll = False
def __init__(self, device: XknxDevice) -> None:
"""Set up device."""
self._device = device
self._unique_id: str | None = None
@property
def name(self) -> str:
@ -30,16 +31,6 @@ class KnxEntity(Entity):
knx_module = cast(KNXModule, self.hass.data[DOMAIN])
return knx_module.connected
@property
def should_poll(self) -> bool:
"""No polling needed within KNX."""
return False
@property
def unique_id(self) -> str | None:
"""Return the unique id of the device."""
return self._unique_id
async def async_update(self) -> None:
"""Request a state update from KNX bus."""
await self._device.sync()

View File

@ -43,15 +43,12 @@ async def async_setup_platform(
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
_async_migrate_unique_id(hass, platform_config)
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXLight(xknx, entity_config))
async_add_entities(entities)
_async_migrate_unique_id(hass, platform_config)
async_add_entities(
KNXLight(xknx, entity_config) for entity_config in platform_config
)
@callback
@ -223,19 +220,21 @@ def _create_light(xknx: XKNX, config: ConfigType) -> XknxLight:
class KNXLight(KnxEntity, LightEntity):
"""Representation of a KNX light."""
_device: XknxLight
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of KNX light."""
self._device: XknxLight
super().__init__(_create_light(xknx, config))
self._unique_id = self._device_unique_id()
self._min_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
self._max_kelvin: int = config[LightSchema.CONF_MAX_KELVIN]
self._min_mireds = color_util.color_temperature_kelvin_to_mired(
self._max_kelvin
)
self._max_mireds = color_util.color_temperature_kelvin_to_mired(
self._min_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
self._attr_max_mireds = color_util.color_temperature_kelvin_to_mired(
self._min_kelvin
)
self._attr_min_mireds = color_util.color_temperature_kelvin_to_mired(
self._max_kelvin
)
self._attr_unique_id = self._device_unique_id()
def _device_unique_id(self) -> str:
"""Return unique id for this device."""
@ -311,16 +310,6 @@ class KNXLight(KnxEntity, LightEntity):
)
return None
@property
def min_mireds(self) -> int:
"""Return the coldest color temp this light supports in mireds."""
return self._min_mireds
@property
def max_mireds(self) -> int:
"""Return the warmest color temp this light supports in mireds."""
return self._max_mireds
@property
def color_mode(self) -> str | None:
"""Return the color mode of the light."""

View File

@ -27,7 +27,6 @@ async def async_setup_platform(
"""Set up number entities for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
@ -51,25 +50,25 @@ def _create_numeric_value(xknx: XKNX, config: ConfigType) -> NumericValue:
class KNXNumber(KnxEntity, NumberEntity, RestoreEntity):
"""Representation of a KNX number."""
_device: NumericValue
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize a KNX number."""
self._device: NumericValue
super().__init__(_create_numeric_value(xknx, config))
self._unique_id = f"{self._device.sensor_value.group_address}"
self._attr_min_value = config.get(
NumberSchema.CONF_MIN,
self._device.sensor_value.dpt_class.value_min,
)
self._attr_max_value = config.get(
NumberSchema.CONF_MAX,
self._device.sensor_value.dpt_class.value_max,
)
self._attr_min_value = config.get(
NumberSchema.CONF_MIN,
self._device.sensor_value.dpt_class.value_min,
)
self._attr_step = config.get(
NumberSchema.CONF_STEP,
self._device.sensor_value.dpt_class.resolution,
)
self._device.sensor_value.value = max(0, self.min_value)
self._attr_unique_id = str(self._device.sensor_value.group_address)
self._device.sensor_value.value = max(0, self._attr_min_value)
async def async_added_to_hass(self) -> None:
"""Restore last state."""

View File

@ -26,23 +26,21 @@ async def async_setup_platform(
"""Set up the scenes for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXScene(xknx, entity_config))
async_add_entities(entities)
async_add_entities(
KNXScene(xknx, entity_config) for entity_config in platform_config
)
class KNXScene(KnxEntity, Scene):
"""Representation of a KNX scene."""
_device: XknxScene
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Init KNX scene."""
self._device: XknxScene
super().__init__(
device=XknxScene(
xknx,
@ -51,7 +49,7 @@ class KNXScene(KnxEntity, Scene):
scene_number=config[SceneSchema.CONF_SCENE_NUMBER],
)
)
self._unique_id = (
self._attr_unique_id = (
f"{self._device.scene_value.group_address}_{self._device.scene_number}"
)

View File

@ -28,10 +28,9 @@ async def async_setup_platform(
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up number entities for KNX platform."""
"""Set up select entities for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
@ -54,20 +53,20 @@ def _create_raw_value(xknx: XKNX, config: ConfigType) -> RawValue:
class KNXSelect(KnxEntity, SelectEntity, RestoreEntity):
"""Representation of a KNX number."""
"""Representation of a KNX select."""
_device: RawValue
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize a KNX number."""
self._device: RawValue
"""Initialize a KNX select."""
super().__init__(_create_raw_value(xknx, config))
self._unique_id = f"{self._device.remote_value.group_address}"
self._option_payloads: dict[str, int] = {
option[SelectSchema.CONF_OPTION]: option[SelectSchema.CONF_PAYLOAD]
for option in config[SelectSchema.CONF_OPTIONS]
}
self._attr_options = list(self._option_payloads)
self._attr_current_option = None
self._attr_unique_id = str(self._device.remote_value.group_address)
async def async_added_to_hass(self) -> None:
"""Restore last state."""
@ -98,7 +97,7 @@ class KNXSelect(KnxEntity, SelectEntity, RestoreEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
if payload := self._option_payloads.get(option):
await self._device.set(payload)
return
raise ValueError(f"Invalid option for {self.entity_id}: {option}")
payload = self._option_payloads.get(option)
if payload is None:
raise ValueError(f"Invalid option for {self.entity_id}: {option}")
await self._device.set(payload)

View File

@ -27,15 +27,12 @@ async def async_setup_platform(
"""Set up sensor(s) for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXSensor(xknx, entity_config))
async_add_entities(entities)
async_add_entities(
KNXSensor(xknx, entity_config) for entity_config in platform_config
)
def _create_sensor(xknx: XKNX, config: ConfigType) -> XknxSensor:
@ -53,30 +50,25 @@ def _create_sensor(xknx: XKNX, config: ConfigType) -> XknxSensor:
class KNXSensor(KnxEntity, SensorEntity):
"""Representation of a KNX sensor."""
_device: XknxSensor
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of a KNX sensor."""
self._device: XknxSensor
super().__init__(_create_sensor(xknx, config))
self._unique_id = f"{self._device.sensor_value.group_address_state}"
self._attr_device_class = (
self._device.ha_device_class()
if self._device.ha_device_class() in DEVICE_CLASSES
else None
)
self._attr_force_update = self._device.always_callback
self._attr_unique_id = str(self._device.sensor_value.group_address_state)
self._attr_unit_of_measurement = self._device.unit_of_measurement()
@property
def state(self) -> StateType:
"""Return the state of the sensor."""
return self._device.resolve_state()
@property
def unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in."""
return self._device.unit_of_measurement()
@property
def device_class(self) -> str | None:
"""Return the device class of the sensor."""
device_class = self._device.ha_device_class()
if device_class in DEVICE_CLASSES:
return device_class
return None
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return device specific state attributes."""
@ -88,13 +80,3 @@ class KNXSensor(KnxEntity, SensorEntity):
dt.as_utc(self._device.last_telegram.timestamp)
)
return attr
@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

View File

@ -27,23 +27,21 @@ async def async_setup_platform(
"""Set up switch(es) for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXSwitch(xknx, entity_config))
async_add_entities(entities)
async_add_entities(
KNXSwitch(xknx, entity_config) for entity_config in platform_config
)
class KNXSwitch(KnxEntity, SwitchEntity, RestoreEntity):
"""Representation of a KNX switch."""
_device: XknxSwitch
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of KNX switch."""
self._device: XknxSwitch
super().__init__(
device=XknxSwitch(
xknx,
@ -53,7 +51,7 @@ class KNXSwitch(KnxEntity, SwitchEntity, RestoreEntity):
invert=config[SwitchSchema.CONF_INVERT],
)
)
self._unique_id = f"{self._device.switch.group_address}"
self._attr_unique_id = str(self._device.switch.group_address)
async def async_added_to_hass(self) -> None:
"""Restore last state."""

View File

@ -24,15 +24,12 @@ async def async_setup_platform(
"""Set up weather entities for KNX platform."""
if not discovery_info or not discovery_info["platform_config"]:
return
platform_config = discovery_info["platform_config"]
xknx: XKNX = hass.data[DOMAIN].xknx
entities = []
for entity_config in platform_config:
entities.append(KNXWeather(xknx, entity_config))
async_add_entities(entities)
async_add_entities(
KNXWeather(xknx, entity_config) for entity_config in platform_config
)
def _create_weather(xknx: XKNX, config: ConfigType) -> XknxWeather:
@ -74,22 +71,19 @@ def _create_weather(xknx: XKNX, config: ConfigType) -> XknxWeather:
class KNXWeather(KnxEntity, WeatherEntity):
"""Representation of a KNX weather device."""
_device: XknxWeather
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
"""Initialize of a KNX sensor."""
self._device: XknxWeather
super().__init__(_create_weather(xknx, config))
self._unique_id = f"{self._device._temperature.group_address_state}"
self._attr_unique_id = str(self._device._temperature.group_address_state)
@property
def temperature(self) -> float | None:
"""Return current temperature."""
return self._device.temperature
@property
def temperature_unit(self) -> str:
"""Return temperature unit."""
return TEMP_CELSIUS
@property
def pressure(self) -> float | None:
"""Return current air pressure."""