Add KNX notify entity UI configuration (#177256)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Matthias Alphart
2026-07-26 18:02:04 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 2e99a5e2aa
commit 12b85bbf3f
7 changed files with 188 additions and 23 deletions
+1
View File
@@ -201,6 +201,7 @@ SUPPORTED_PLATFORMS_UI: Final = {
Platform.FAN,
Platform.DATETIME,
Platform.LIGHT,
Platform.NOTIFY,
Platform.NUMBER,
Platform.SCENE,
Platform.SENSOR,
+70 -22
View File
@@ -2,19 +2,23 @@
from typing import override
from xknx import XKNX
from xknx.devices import Notification as XknxNotification
from homeassistant import config_entries
from homeassistant.components.notify import NotifyEntity
from homeassistant.const import CONF_NAME, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
async_get_current_platform,
)
from homeassistant.helpers.typing import ConfigType
from .const import KNX_ADDRESS, KNX_MODULE_KEY
from .entity import KnxYamlEntity
from .const import DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
from .knx_module import KNXModule
from .storage.const import CONF_ENTITY, CONF_GA_SEND
from .storage.util import ConfigExtractor
async def async_setup_entry(
@@ -24,36 +28,80 @@ async def async_setup_entry(
) -> None:
"""Set up notify(s) for KNX platform."""
knx_module = hass.data[KNX_MODULE_KEY]
config: list[ConfigType] = knx_module.config_yaml[Platform.NOTIFY]
async_add_entities(KNXNotify(knx_module, entity_config) for entity_config in config)
def _create_notification_instance(xknx: XKNX, config: ConfigType) -> XknxNotification:
"""Return a KNX Notification to be used within XKNX."""
return XknxNotification(
xknx,
name=config[CONF_NAME],
group_address=config[KNX_ADDRESS],
value_type=config[CONF_TYPE],
platform = async_get_current_platform()
knx_module.config_store.add_platform(
platform=Platform.NOTIFY,
controller=KnxUiEntityPlatformController(
knx_module=knx_module,
entity_platform=platform,
entity_class=KnxUiNotify,
),
)
entities: list[KnxYamlEntity | KnxUiEntity] = []
if yaml_platform_config := knx_module.config_yaml.get(Platform.NOTIFY):
entities.extend(
KnxYamlNotify(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.NOTIFY):
entities.extend(
KnxUiNotify(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
)
if entities:
async_add_entities(entities)
class KNXNotify(KnxYamlEntity, NotifyEntity):
class _KnxNotify(NotifyEntity):
"""Representation of a KNX notification entity."""
_device: XknxNotification
@override
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a notification to knx bus."""
await self._device.set(message)
class KnxYamlNotify(_KnxNotify, KnxYamlEntity):
"""Representation of a KNX notification entity configured from YAML."""
_device: XknxNotification
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
"""Initialize a KNX notification."""
self._device = _create_notification_instance(knx_module.xknx, config)
self._device = XknxNotification(
knx_module.xknx,
name=config[CONF_NAME],
group_address=config[KNX_ADDRESS],
value_type=config[CONF_TYPE],
)
super().__init__(
knx_module=knx_module,
unique_id=str(self._device.remote_value.group_address),
entity_config=config,
)
@override
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a notification to knx bus."""
await self._device.set(message)
class KnxUiNotify(_KnxNotify, KnxUiEntity):
"""Representation of a KNX notification entity configured from UI."""
_device: XknxNotification
def __init__(
self, knx_module: KNXModule, unique_id: str, config: ConfigType
) -> None:
"""Initialize a KNX notification."""
super().__init__(
knx_module=knx_module,
unique_id=unique_id,
entity_config=config[CONF_ENTITY],
)
knx_conf = ConfigExtractor(config[DOMAIN])
self._device = XknxNotification(
knx_module.xknx,
name=config[CONF_ENTITY][CONF_NAME],
group_address=knx_conf.get_write(CONF_GA_SEND),
value_type=knx_conf.get_dpt(CONF_GA_SEND),
)
@@ -485,6 +485,15 @@ LIGHT_KNX_SCHEMA = AllSerializeFirst(
)
NOTIFY_KNX_SCHEMA = vol.Schema(
{
vol.Required(CONF_GA_SEND): GASelector(
state=False, passive=False, write_required=True, dpt=["string"]
),
}
)
def _number_limit_sub_validator(config: dict) -> dict:
"""Validate min, max, and step values for a number entity."""
dpt = config[CONF_GA_SENSOR][CONF_DPT]
@@ -802,6 +811,7 @@ KNX_SCHEMA_FOR_PLATFORM = {
Platform.DATETIME: DATETIME_KNX_SCHEMA,
Platform.FAN: FAN_KNX_SCHEMA,
Platform.LIGHT: LIGHT_KNX_SCHEMA,
Platform.NOTIFY: NOTIFY_KNX_SCHEMA,
Platform.NUMBER: NUMBER_KNX_SCHEMA,
Platform.SCENE: SCENE_KNX_SCHEMA,
Platform.SENSOR: SENSOR_KNX_SCHEMA,
@@ -860,6 +860,15 @@
}
}
},
"notify": {
"description": "The KNX notify platform allows you to send text messages to the KNX bus.",
"knx": {
"ga_send": {
"description": "The group address messages are sent to.",
"label": "Address"
}
}
},
"number": {
"description": "Entity for numeric datapoints. Temperature, percent, etc.",
"knx": {
@@ -0,0 +1,27 @@
{
"version": 2,
"minor_version": 4,
"key": "knx/config_store.json",
"data": {
"entities": {
"notify": {
"knx_es_01KCXYT0WEJEQQ13SGCY1NHCYX": {
"entity": {
"name": "test",
"device_info": null,
"entity_category": null
},
"knx": {
"ga_send": {
"write": "1/0/0",
"dpt": "16.001",
"passive": []
}
}
}
}
},
"expose": {},
"time_server": {}
}
}
@@ -1649,6 +1649,30 @@
'type': 'result',
})
# ---
# name: test_knx_get_schema[notify]
dict({
'id': 1,
'result': list([
dict({
'name': 'ga_send',
'options': dict({
'dptClasses': list([
'string',
]),
'passive': False,
'state': False,
'write': dict({
'required': True,
}),
}),
'required': True,
'type': 'knx_group_address',
}),
]),
'success': True,
'type': 'result',
})
# ---
# name: test_knx_get_schema[number]
dict({
'id': 1,
+47 -1
View File
@@ -3,9 +3,10 @@
from homeassistant.components import notify
from homeassistant.components.knx.const import KNX_ADDRESS
from homeassistant.components.knx.schema import NotifySchema
from homeassistant.const import CONF_NAME, CONF_TYPE
from homeassistant.const import CONF_NAME, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant
from . import KnxEntityGenerator
from .conftest import KNXTestKit
@@ -96,3 +97,48 @@ async def test_notify_multiple_sends_with_different_encodings(
"1/0/1",
(71, 228, 110, 115, 101, 102, 252, 223, 99, 104, 101, 110, 0, 0),
)
async def test_notify_ui_create(
hass: HomeAssistant,
knx: KNXTestKit,
create_ui_entity: KnxEntityGenerator,
) -> None:
"""Test creating a notify entity from the UI."""
await knx.setup_integration()
await create_ui_entity(
platform=Platform.NOTIFY,
entity_data={"name": "test"},
knx_data={"ga_send": {"write": "1/0/0", "dpt": "16.000"}},
)
await hass.services.async_call(
notify.DOMAIN,
notify.SERVICE_SEND_MESSAGE,
{"entity_id": "notify.test", notify.ATTR_MESSAGE: "Home Assistant"},
blocking=True,
)
await knx.assert_write(
"1/0/0",
(72, 111, 109, 101, 32, 65, 115, 115, 105, 115, 116, 97, 110, 116),
)
async def test_notify_ui_load(
hass: HomeAssistant,
knx: KNXTestKit,
) -> None:
"""Test loading a notify entity from storage."""
await knx.setup_integration(config_store_fixture="config_store_notify.json")
assert hass.states.get("notify.test")
await hass.services.async_call(
notify.DOMAIN,
notify.SERVICE_SEND_MESSAGE,
{"entity_id": "notify.test", notify.ATTR_MESSAGE: "Home Assistant"},
blocking=True,
)
await knx.assert_write(
"1/0/0",
(72, 111, 109, 101, 32, 65, 115, 115, 105, 115, 116, 97, 110, 116),
)