Address post-merge reviews for KNX integration (#123038)

pull/123066/head
Matthias Alphart 2024-08-02 12:53:39 +02:00 committed by GitHub
parent 4a06e20318
commit 42234e6a09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 211 additions and 125 deletions

View File

@ -302,7 +302,7 @@ class KNXModule:
self.entry = entry
self.project = KNXProject(hass=hass, entry=entry)
self.config_store = KNXConfigStore(hass=hass, entry=entry)
self.config_store = KNXConfigStore(hass=hass, config_entry=entry)
self.xknx = XKNX(
connection_config=self.connection_config(),

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from typing import Any
from xknx import XKNX
from xknx.devices import BinarySensor as XknxBinarySensor
from homeassistant import config_entries
@ -23,6 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import ATTR_COUNTER, ATTR_SOURCE, DATA_KNX_CONFIG, DOMAIN
from .knx_entity import KnxEntity
from .schema import BinarySensorSchema
@ -34,11 +34,11 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the KNX binary sensor platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: ConfigType = hass.data[DATA_KNX_CONFIG]
async_add_entities(
KNXBinarySensor(xknx, entity_config)
KNXBinarySensor(knx_module, entity_config)
for entity_config in config[Platform.BINARY_SENSOR]
)
@ -48,11 +48,12 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity, RestoreEntity):
_device: XknxBinarySensor
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of KNX binary sensor."""
super().__init__(
knx_module=knx_module,
device=XknxBinarySensor(
xknx,
xknx=knx_module.xknx,
name=config[CONF_NAME],
group_address_state=config[BinarySensorSchema.CONF_STATE_ADDRESS],
invert=config[BinarySensorSchema.CONF_INVERT],
@ -62,7 +63,7 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity, RestoreEntity):
],
context_timeout=config.get(BinarySensorSchema.CONF_CONTEXT_TIMEOUT),
reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER),
)
),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_device_class = config.get(CONF_DEVICE_CLASS)

View File

@ -2,7 +2,6 @@
from __future__ import annotations
from xknx import XKNX
from xknx.devices import RawValue as XknxRawValue
from homeassistant import config_entries
@ -12,6 +11,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import CONF_PAYLOAD_LENGTH, DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity
@ -22,11 +22,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the KNX binary sensor platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: ConfigType = hass.data[DATA_KNX_CONFIG]
async_add_entities(
KNXButton(xknx, entity_config) for entity_config in config[Platform.BUTTON]
KNXButton(knx_module, entity_config)
for entity_config in config[Platform.BUTTON]
)
@ -35,15 +36,16 @@ class KNXButton(KnxEntity, ButtonEntity):
_device: XknxRawValue
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX button."""
super().__init__(
knx_module=knx_module,
device=XknxRawValue(
xknx,
xknx=knx_module.xknx,
name=config[CONF_NAME],
payload_length=config[CONF_PAYLOAD_LENGTH],
group_address=config[KNX_ADDRESS],
)
),
)
self._payload = config[CONF_PAYLOAD]
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)

View File

@ -27,6 +27,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONTROLLER_MODES,
CURRENT_HVAC_ACTIONS,
@ -48,10 +49,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up climate(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.CLIMATE]
async_add_entities(KNXClimate(xknx, entity_config) for entity_config in config)
async_add_entities(
KNXClimate(knx_module, entity_config) for entity_config in config
)
def _create_climate(xknx: XKNX, config: ConfigType) -> XknxClimate:
@ -137,9 +140,12 @@ class KNXClimate(KnxEntity, ClimateEntity):
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of a KNX climate device."""
super().__init__(_create_climate(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_climate(knx_module.xknx, config),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
if self._device.supports_on_off:

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import Callable
from typing import Any
from xknx import XKNX
from xknx.devices import Cover as XknxCover
from homeassistant import config_entries
@ -26,6 +25,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import DATA_KNX_CONFIG, DOMAIN
from .knx_entity import KnxEntity
from .schema import CoverSchema
@ -37,10 +37,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up cover(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.COVER]
async_add_entities(KNXCover(xknx, entity_config) for entity_config in config)
async_add_entities(KNXCover(knx_module, entity_config) for entity_config in config)
class KNXCover(KnxEntity, CoverEntity):
@ -48,11 +48,12 @@ class KNXCover(KnxEntity, CoverEntity):
_device: XknxCover
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize the cover."""
super().__init__(
knx_module=knx_module,
device=XknxCover(
xknx,
xknx=knx_module.xknx,
name=config[CONF_NAME],
group_address_long=config.get(CoverSchema.CONF_MOVE_LONG_ADDRESS),
group_address_short=config.get(CoverSchema.CONF_MOVE_SHORT_ADDRESS),
@ -70,7 +71,7 @@ class KNXCover(KnxEntity, CoverEntity):
invert_updown=config[CoverSchema.CONF_INVERT_UPDOWN],
invert_position=config[CoverSchema.CONF_INVERT_POSITION],
invert_angle=config[CoverSchema.CONF_INVERT_ANGLE],
)
),
)
self._unsubscribe_auto_updater: Callable[[], None] | None = None

View File

@ -22,6 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONF_RESPOND_TO_READ,
CONF_STATE_ADDRESS,
@ -39,10 +40,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entities for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.DATE]
async_add_entities(KNXDateEntity(xknx, entity_config) for entity_config in config)
async_add_entities(
KNXDateEntity(knx_module, entity_config) for entity_config in config
)
def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxDateDevice:
@ -63,9 +66,12 @@ class KNXDateEntity(KnxEntity, DateEntity, RestoreEntity):
_device: XknxDateDevice
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX time."""
super().__init__(_create_xknx_device(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_xknx_device(knx_module.xknx, config),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_unique_id = str(self._device.remote_value.group_address)

View File

@ -23,6 +23,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
import homeassistant.util.dt as dt_util
from . import KNXModule
from .const import (
CONF_RESPOND_TO_READ,
CONF_STATE_ADDRESS,
@ -40,11 +41,11 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entities for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.DATETIME]
async_add_entities(
KNXDateTimeEntity(xknx, entity_config) for entity_config in config
KNXDateTimeEntity(knx_module, entity_config) for entity_config in config
)
@ -66,9 +67,12 @@ class KNXDateTimeEntity(KnxEntity, DateTimeEntity, RestoreEntity):
_device: XknxDateTimeDevice
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX time."""
super().__init__(_create_xknx_device(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_xknx_device(knx_module.xknx, config),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_unique_id = str(self._device.remote_value.group_address)

View File

@ -5,7 +5,6 @@ from __future__ import annotations
import math
from typing import Any, Final
from xknx import XKNX
from xknx.devices import Fan as XknxFan
from homeassistant import config_entries
@ -20,6 +19,7 @@ from homeassistant.util.percentage import (
)
from homeassistant.util.scaling import int_states_in_range
from . import KNXModule
from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity
from .schema import FanSchema
@ -33,10 +33,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up fan(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.FAN]
async_add_entities(KNXFan(xknx, entity_config) for entity_config in config)
async_add_entities(KNXFan(knx_module, entity_config) for entity_config in config)
class KNXFan(KnxEntity, FanEntity):
@ -45,12 +45,13 @@ class KNXFan(KnxEntity, FanEntity):
_device: XknxFan
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of KNX fan."""
max_step = config.get(FanSchema.CONF_MAX_STEP)
super().__init__(
knx_module=knx_module,
device=XknxFan(
xknx,
xknx=knx_module.xknx,
name=config[CONF_NAME],
group_address_speed=config.get(KNX_ADDRESS),
group_address_speed_state=config.get(FanSchema.CONF_STATE_ADDRESS),
@ -61,7 +62,7 @@ class KNXFan(KnxEntity, FanEntity):
FanSchema.CONF_OSCILLATION_STATE_ADDRESS
),
max_step=max_step,
)
),
)
# FanSpeedMode.STEP if max_step is set
self._step_range: tuple[int, int] | None = (1, max_step) if max_step else None

View File

@ -2,23 +2,29 @@
from __future__ import annotations
from typing import cast
from typing import TYPE_CHECKING
from xknx.devices import Device as XknxDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from . import KNXModule
from .const import DOMAIN
if TYPE_CHECKING:
from . import KNXModule
SIGNAL_ENTITY_REMOVE = f"{DOMAIN}_entity_remove_signal.{{}}"
class KnxEntity(Entity):
"""Representation of a KNX entity."""
_attr_should_poll = False
def __init__(self, device: XknxDevice) -> None:
def __init__(self, knx_module: KNXModule, device: XknxDevice) -> None:
"""Set up device."""
self._knx_module = knx_module
self._device = device
@property
@ -29,8 +35,7 @@ class KnxEntity(Entity):
@property
def available(self) -> bool:
"""Return True if entity is available."""
knx_module = cast(KNXModule, self.hass.data[DOMAIN])
return knx_module.connected
return self._knx_module.connected
async def async_update(self) -> None:
"""Request a state update from KNX bus."""
@ -44,8 +49,29 @@ class KnxEntity(Entity):
"""Store register state change callback and start device object."""
self._device.register_device_updated_cb(self.after_update_callback)
self._device.xknx.devices.async_add(self._device)
# super call needed to have methods of mulit-inherited classes called
# eg. for restoring state (like _KNXSwitch)
await super().async_added_to_hass()
async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed."""
self._device.unregister_device_updated_cb(self.after_update_callback)
self._device.xknx.devices.async_remove(self._device)
class KnxUIEntity(KnxEntity):
"""Representation of a KNX UI entity."""
_attr_unique_id: str
async def async_added_to_hass(self) -> None:
"""Register callbacks when entity added to hass."""
await super().async_added_to_hass()
self._knx_module.config_store.entities.add(self._attr_unique_id)
self.async_on_remove(
async_dispatcher_connect(
self.hass,
SIGNAL_ENTITY_REMOVE.format(self._attr_unique_id),
self.async_remove,
)
)

View File

@ -27,7 +27,7 @@ import homeassistant.util.color as color_util
from . import KNXModule
from .const import CONF_SYNC_STATE, DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS, ColorTempModes
from .knx_entity import KnxEntity
from .knx_entity import KnxEntity, KnxUIEntity
from .schema import LightSchema
from .storage.const import (
CONF_COLOR_TEMP_MAX,
@ -65,10 +65,10 @@ async def async_setup_entry(
knx_module: KNXModule = hass.data[DOMAIN]
entities: list[KnxEntity] = []
if yaml_config := hass.data[DATA_KNX_CONFIG].get(Platform.LIGHT):
if yaml_platform_config := hass.data[DATA_KNX_CONFIG].get(Platform.LIGHT):
entities.extend(
KnxYamlLight(knx_module.xknx, entity_config)
for entity_config in yaml_config
KnxYamlLight(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.LIGHT):
entities.extend(
@ -294,7 +294,7 @@ def _create_ui_light(xknx: XKNX, knx_config: ConfigType, name: str) -> XknxLight
)
class _KnxLight(KnxEntity, LightEntity):
class _KnxLight(LightEntity):
"""Representation of a KNX light."""
_attr_max_color_temp_kelvin: int
@ -519,14 +519,17 @@ class _KnxLight(KnxEntity, LightEntity):
await self._device.set_off()
class KnxYamlLight(_KnxLight):
class KnxYamlLight(_KnxLight, KnxEntity):
"""Representation of a KNX light."""
_device: XknxLight
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of KNX light."""
super().__init__(_create_yaml_light(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_yaml_light(knx_module.xknx, config),
)
self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN]
self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
@ -543,20 +546,21 @@ class KnxYamlLight(_KnxLight):
)
class KnxUiLight(_KnxLight):
class KnxUiLight(_KnxLight, KnxUIEntity):
"""Representation of a KNX light."""
_device: XknxLight
_attr_has_entity_name = True
_device: XknxLight
def __init__(
self, knx_module: KNXModule, unique_id: str, config: ConfigType
) -> None:
"""Initialize of KNX light."""
super().__init__(
_create_ui_light(
knx_module=knx_module,
device=_create_ui_light(
knx_module.xknx, config[DOMAIN], config[CONF_ENTITY][CONF_NAME]
)
),
)
self._attr_max_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MAX]
self._attr_min_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MIN]
@ -565,5 +569,3 @@ class KnxUiLight(_KnxLight):
self._attr_unique_id = unique_id
if device_info := config[CONF_ENTITY].get(CONF_DEVICE_INFO):
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_info)})
knx_module.config_store.entities[unique_id] = self

View File

@ -18,6 +18,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import KNXModule
from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity
@ -44,7 +45,7 @@ async def async_get_service(
class KNXNotificationService(BaseNotificationService):
"""Implement demo notification service."""
"""Implement notification service."""
def __init__(self, devices: list[XknxNotification]) -> None:
"""Initialize the service."""
@ -86,10 +87,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up notify(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.NOTIFY]
async_add_entities(KNXNotify(xknx, entity_config) for entity_config in config)
async_add_entities(KNXNotify(knx_module, entity_config) for entity_config in config)
def _create_notification_instance(xknx: XKNX, config: ConfigType) -> XknxNotification:
@ -107,9 +108,12 @@ class KNXNotify(KnxEntity, NotifyEntity):
_device: XknxNotification
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX notification."""
super().__init__(_create_notification_instance(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_notification_instance(knx_module.xknx, config),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_unique_id = str(self._device.remote_value.group_address)

View File

@ -22,6 +22,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONF_RESPOND_TO_READ,
CONF_STATE_ADDRESS,
@ -39,10 +40,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up number(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.NUMBER]
async_add_entities(KNXNumber(xknx, entity_config) for entity_config in config)
async_add_entities(KNXNumber(knx_module, entity_config) for entity_config in config)
def _create_numeric_value(xknx: XKNX, config: ConfigType) -> NumericValue:
@ -62,9 +63,12 @@ class KNXNumber(KnxEntity, RestoreNumber):
_device: NumericValue
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX number."""
super().__init__(_create_numeric_value(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_numeric_value(knx_module.xknx, config),
)
self._attr_native_max_value = config.get(
NumberSchema.CONF_MAX,
self._device.sensor_value.dpt_class.value_max,

View File

@ -8,9 +8,11 @@ from typing import Final
from xknx import XKNX
from xknx.dpt import DPTBase
from xknx.telegram.address import DeviceAddressableType
from xknxproject import XKNXProj
from xknxproject.models import (
Device,
DPTType,
GroupAddress as GroupAddressModel,
KNXProject as KNXProjectModel,
ProjectInfo,
@ -89,7 +91,7 @@ class KNXProject:
self.devices = project["devices"]
self.info = project["info"]
xknx.group_address_dpt.clear()
xknx_ga_dict = {}
xknx_ga_dict: dict[DeviceAddressableType, DPTType] = {}
for ga_model in project["group_addresses"].values():
ga_info = _create_group_address_info(ga_model)
@ -97,7 +99,7 @@ class KNXProject:
if (dpt_model := ga_model.get("dpt")) is not None:
xknx_ga_dict[ga_model["address"]] = dpt_model
xknx.group_address_dpt.set(xknx_ga_dict) # type: ignore[arg-type]
xknx.group_address_dpt.set(xknx_ga_dict)
_LOGGER.debug(
"Loaded KNX project data with %s group addresses from storage",

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from typing import Any
from xknx import XKNX
from xknx.devices import Scene as XknxScene
from homeassistant import config_entries
@ -14,6 +13,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity
from .schema import SceneSchema
@ -25,10 +25,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up scene(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.SCENE]
async_add_entities(KNXScene(xknx, entity_config) for entity_config in config)
async_add_entities(KNXScene(knx_module, entity_config) for entity_config in config)
class KNXScene(KnxEntity, Scene):
@ -36,15 +36,16 @@ class KNXScene(KnxEntity, Scene):
_device: XknxScene
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Init KNX scene."""
super().__init__(
knx_module=knx_module,
device=XknxScene(
xknx,
xknx=knx_module.xknx,
name=config[CONF_NAME],
group_address=config[KNX_ADDRESS],
scene_number=config[SceneSchema.CONF_SCENE_NUMBER],
)
),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_unique_id = (

View File

@ -20,6 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONF_PAYLOAD_LENGTH,
CONF_RESPOND_TO_READ,
@ -39,10 +40,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up select(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.SELECT]
async_add_entities(KNXSelect(xknx, entity_config) for entity_config in config)
async_add_entities(KNXSelect(knx_module, entity_config) for entity_config in config)
def _create_raw_value(xknx: XKNX, config: ConfigType) -> RawValue:
@ -63,9 +64,12 @@ class KNXSelect(KnxEntity, SelectEntity, RestoreEntity):
_device: RawValue
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX select."""
super().__init__(_create_raw_value(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_raw_value(knx_module.xknx, config),
)
self._option_payloads: dict[str, int] = {
option[SelectSchema.CONF_OPTION]: option[CONF_PAYLOAD]
for option in config[SelectSchema.CONF_OPTIONS]

View File

@ -116,17 +116,17 @@ async def async_setup_entry(
) -> None:
"""Set up sensor(s) for KNX platform."""
knx_module: KNXModule = hass.data[DOMAIN]
async_add_entities(
entities: list[SensorEntity] = []
entities.extend(
KNXSystemSensor(knx_module, description)
for description in SYSTEM_ENTITY_DESCRIPTIONS
)
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG].get(Platform.SENSOR)
if config:
async_add_entities(
KNXSensor(knx_module.xknx, entity_config) for entity_config in config
entities.extend(
KNXSensor(knx_module, entity_config) for entity_config in config
)
async_add_entities(entities)
def _create_sensor(xknx: XKNX, config: ConfigType) -> XknxSensor:
@ -146,9 +146,12 @@ class KNXSensor(KnxEntity, SensorEntity):
_device: XknxSensor
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of a KNX sensor."""
super().__init__(_create_sensor(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_sensor(knx_module.xknx, config),
)
if device_class := config.get(CONF_DEVICE_CLASS):
self._attr_device_class = device_class
else:

View File

@ -2,21 +2,20 @@
from collections.abc import Callable
import logging
from typing import TYPE_CHECKING, Any, Final, TypedDict
from typing import Any, Final, TypedDict
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PLATFORM, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.storage import Store
from homeassistant.util.ulid import ulid_now
from ..const import DOMAIN
from ..knx_entity import SIGNAL_ENTITY_REMOVE
from .const import CONF_DATA
if TYPE_CHECKING:
from ..knx_entity import KnxEntity
_LOGGER = logging.getLogger(__name__)
STORAGE_VERSION: Final = 1
@ -40,15 +39,16 @@ class KNXConfigStore:
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
config_entry: ConfigEntry,
) -> None:
"""Initialize config store."""
self.hass = hass
self.config_entry = config_entry
self._store = Store[KNXConfigStoreModel](hass, STORAGE_VERSION, STORAGE_KEY)
self.data = KNXConfigStoreModel(entities={})
# entities and async_add_entity are filled by platform setups
self.entities: dict[str, KnxEntity] = {} # unique_id as key
# entities and async_add_entity are filled by platform / entity setups
self.entities: set[str] = set() # unique_id as values
self.async_add_entity: dict[
Platform, Callable[[str, dict[str, Any]], None]
] = {}
@ -108,7 +108,7 @@ class KNXConfigStore:
raise ConfigStoreException(
f"Entity not found in storage: {entity_id} - {unique_id}"
)
await self.entities.pop(unique_id).async_remove()
async_dispatcher_send(self.hass, SIGNAL_ENTITY_REMOVE.format(unique_id))
self.async_add_entity[platform](unique_id, data)
# store data after entity is added to make sure config doesn't raise exceptions
self.data["entities"][platform][unique_id] = data
@ -126,7 +126,7 @@ class KNXConfigStore:
f"Entity not found in {entry.domain}: {entry.unique_id}"
) from err
try:
del self.entities[entry.unique_id]
self.entities.remove(entry.unique_id)
except KeyError:
_LOGGER.warning("Entity not initialized when deleted: %s", entity_id)
entity_registry.async_remove(entity_id)
@ -134,10 +134,14 @@ class KNXConfigStore:
def get_entity_entries(self) -> list[er.RegistryEntry]:
"""Get entity_ids of all configured entities by platform."""
entity_registry = er.async_get(self.hass)
return [
entity.registry_entry
for entity in self.entities.values()
if entity.registry_entry is not None
registry_entry
for registry_entry in er.async_entries_for_config_entry(
entity_registry, self.config_entry.entry_id
)
if registry_entry.unique_id in self.entities
]

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from typing import Any
from xknx import XKNX
from xknx.devices import Switch as XknxSwitch
from homeassistant import config_entries
@ -33,7 +32,7 @@ from .const import (
DOMAIN,
KNX_ADDRESS,
)
from .knx_entity import KnxEntity
from .knx_entity import KnxEntity, KnxUIEntity
from .schema import SwitchSchema
from .storage.const import (
CONF_DEVICE_INFO,
@ -54,10 +53,10 @@ async def async_setup_entry(
knx_module: KNXModule = hass.data[DOMAIN]
entities: list[KnxEntity] = []
if yaml_config := hass.data[DATA_KNX_CONFIG].get(Platform.SWITCH):
if yaml_platform_config := hass.data[DATA_KNX_CONFIG].get(Platform.SWITCH):
entities.extend(
KnxYamlSwitch(knx_module.xknx, entity_config)
for entity_config in yaml_config
KnxYamlSwitch(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.SWITCH):
entities.extend(
@ -75,7 +74,7 @@ async def async_setup_entry(
knx_module.config_store.async_add_entity[Platform.SWITCH] = add_new_ui_switch
class _KnxSwitch(KnxEntity, SwitchEntity, RestoreEntity):
class _KnxSwitch(SwitchEntity, RestoreEntity):
"""Base class for a KNX switch."""
_device: XknxSwitch
@ -103,36 +102,41 @@ class _KnxSwitch(KnxEntity, SwitchEntity, RestoreEntity):
await self._device.set_off()
class KnxYamlSwitch(_KnxSwitch):
class KnxYamlSwitch(_KnxSwitch, KnxEntity):
"""Representation of a KNX switch configured from YAML."""
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
_device: XknxSwitch
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of KNX switch."""
super().__init__(
knx_module=knx_module,
device=XknxSwitch(
xknx,
xknx=knx_module.xknx,
name=config[CONF_NAME],
group_address=config[KNX_ADDRESS],
group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS),
respond_to_read=config[CONF_RESPOND_TO_READ],
invert=config[SwitchSchema.CONF_INVERT],
)
),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
self._attr_unique_id = str(self._device.switch.group_address)
class KnxUiSwitch(_KnxSwitch):
class KnxUiSwitch(_KnxSwitch, KnxUIEntity):
"""Representation of a KNX switch configured from UI."""
_attr_has_entity_name = True
_device: XknxSwitch
def __init__(
self, knx_module: KNXModule, unique_id: str, config: dict[str, Any]
) -> None:
"""Initialize of KNX switch."""
super().__init__(
knx_module=knx_module,
device=XknxSwitch(
knx_module.xknx,
name=config[CONF_ENTITY][CONF_NAME],
@ -144,11 +148,9 @@ class KnxUiSwitch(_KnxSwitch):
respond_to_read=config[DOMAIN][CONF_RESPOND_TO_READ],
sync_state=config[DOMAIN][CONF_SYNC_STATE],
invert=config[DOMAIN][CONF_INVERT],
)
),
)
self._attr_entity_category = config[CONF_ENTITY][CONF_ENTITY_CATEGORY]
self._attr_unique_id = unique_id
if device_info := config[CONF_ENTITY].get(CONF_DEVICE_INFO):
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_info)})
knx_module.config_store.entities[unique_id] = self

View File

@ -22,6 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONF_RESPOND_TO_READ,
CONF_STATE_ADDRESS,
@ -38,10 +39,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensor(s) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.TEXT]
async_add_entities(KNXText(xknx, entity_config) for entity_config in config)
async_add_entities(KNXText(knx_module, entity_config) for entity_config in config)
def _create_notification(xknx: XKNX, config: ConfigType) -> XknxNotification:
@ -62,9 +63,12 @@ class KNXText(KnxEntity, TextEntity, RestoreEntity):
_device: XknxNotification
_attr_native_max = 14
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX text."""
super().__init__(_create_notification(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_notification(knx_module.xknx, config),
)
self._attr_mode = config[CONF_MODE]
self._attr_pattern = (
r"[\u0000-\u00ff]*" # Latin-1

View File

@ -3,7 +3,6 @@
from __future__ import annotations
from datetime import time as dt_time
from typing import Final
from xknx import XKNX
from xknx.devices import TimeDevice as XknxTimeDevice
@ -23,6 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import (
CONF_RESPOND_TO_READ,
CONF_STATE_ADDRESS,
@ -33,8 +33,6 @@ from .const import (
)
from .knx_entity import KnxEntity
_TIME_TRANSLATION_FORMAT: Final = "%H:%M:%S"
async def async_setup_entry(
hass: HomeAssistant,
@ -42,10 +40,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entities for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.TIME]
async_add_entities(KNXTimeEntity(xknx, entity_config) for entity_config in config)
async_add_entities(
KNXTimeEntity(knx_module, entity_config) for entity_config in config
)
def _create_xknx_device(xknx: XKNX, config: ConfigType) -> XknxTimeDevice:
@ -66,9 +66,12 @@ class KNXTimeEntity(KnxEntity, TimeEntity, RestoreEntity):
_device: XknxTimeDevice
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX time."""
super().__init__(_create_xknx_device(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_xknx_device(knx_module.xknx, config),
)
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_unique_id = str(self._device.remote_value.group_address)

View File

@ -19,6 +19,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from . import KNXModule
from .const import DATA_KNX_CONFIG, DOMAIN
from .knx_entity import KnxEntity
from .schema import WeatherSchema
@ -30,10 +31,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switch(es) for KNX platform."""
xknx: XKNX = hass.data[DOMAIN].xknx
knx_module: KNXModule = hass.data[DOMAIN]
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.WEATHER]
async_add_entities(KNXWeather(xknx, entity_config) for entity_config in config)
async_add_entities(
KNXWeather(knx_module, entity_config) for entity_config in config
)
def _create_weather(xknx: XKNX, config: ConfigType) -> XknxWeather:
@ -80,9 +83,12 @@ class KNXWeather(KnxEntity, WeatherEntity):
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
_attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize of a KNX sensor."""
super().__init__(_create_weather(xknx, config))
super().__init__(
knx_module=knx_module,
device=_create_weather(knx_module.xknx, config),
)
self._attr_unique_id = str(self._device._temperature.group_address_state) # noqa: SLF001
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)