mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 01:38:02 +00:00
Migrate KNX notify service to entity platform (#115665)
This commit is contained in:
parent
b37f7b1ff0
commit
f115525137
@ -197,11 +197,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
[
|
||||
platform
|
||||
for platform in SUPPORTED_PLATFORMS
|
||||
if platform in config and platform not in (Platform.SENSOR, Platform.NOTIFY)
|
||||
if platform in config and platform is not Platform.SENSOR
|
||||
],
|
||||
)
|
||||
|
||||
# set up notify platform, no entry support for notify component yet
|
||||
# set up notify service for backwards compatibility - remove 2024.11
|
||||
if NotifySchema.PLATFORM in config:
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
@ -232,7 +232,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
platform
|
||||
for platform in SUPPORTED_PLATFORMS
|
||||
if platform in hass.data[DATA_KNX_CONFIG]
|
||||
and platform not in (Platform.SENSOR, Platform.NOTIFY)
|
||||
and platform is not Platform.SENSOR
|
||||
],
|
||||
],
|
||||
)
|
||||
|
@ -4,7 +4,7 @@
|
||||
"after_dependencies": ["panel_custom"],
|
||||
"codeowners": ["@Julius2342", "@farmio", "@marvin-w"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["file_upload", "websocket_api"],
|
||||
"dependencies": ["file_upload", "repairs", "websocket_api"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/knx",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP notification services."""
|
||||
"""Support for KNX/IP notifications."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -7,13 +7,16 @@ from typing import Any
|
||||
from xknx import XKNX
|
||||
from xknx.devices import Notification as XknxNotification
|
||||
|
||||
from homeassistant.components.notify import BaseNotificationService
|
||||
from homeassistant.const import CONF_NAME, CONF_TYPE
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.notify import BaseNotificationService, NotifyEntity
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, CONF_TYPE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
|
||||
from .schema import NotifySchema
|
||||
from .knx_entity import KnxEntity
|
||||
from .repairs import migrate_notify_issue
|
||||
|
||||
|
||||
async def async_get_service(
|
||||
@ -25,16 +28,11 @@ async def async_get_service(
|
||||
if discovery_info is None:
|
||||
return None
|
||||
|
||||
if platform_config := hass.data[DATA_KNX_CONFIG].get(NotifySchema.PLATFORM):
|
||||
if platform_config := hass.data[DATA_KNX_CONFIG].get(Platform.NOTIFY):
|
||||
xknx: XKNX = hass.data[DOMAIN].xknx
|
||||
|
||||
notification_devices = [
|
||||
XknxNotification(
|
||||
xknx,
|
||||
name=device_config[CONF_NAME],
|
||||
group_address=device_config[KNX_ADDRESS],
|
||||
value_type=device_config[CONF_TYPE],
|
||||
)
|
||||
_create_notification_instance(xknx, device_config)
|
||||
for device_config in platform_config
|
||||
]
|
||||
return KNXNotificationService(notification_devices)
|
||||
@ -59,6 +57,7 @@ class KNXNotificationService(BaseNotificationService):
|
||||
|
||||
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
|
||||
"""Send a notification to knx bus."""
|
||||
migrate_notify_issue(self.hass)
|
||||
if "target" in kwargs:
|
||||
await self._async_send_to_device(message, kwargs["target"])
|
||||
else:
|
||||
@ -74,3 +73,41 @@ class KNXNotificationService(BaseNotificationService):
|
||||
for device in self.devices:
|
||||
if device.name in names:
|
||||
await device.set(message)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up notify(s) for KNX platform."""
|
||||
xknx: XKNX = hass.data[DOMAIN].xknx
|
||||
config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.NOTIFY]
|
||||
|
||||
async_add_entities(KNXNotify(xknx, 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],
|
||||
)
|
||||
|
||||
|
||||
class KNXNotify(NotifyEntity, KnxEntity):
|
||||
"""Representation of a KNX notification entity."""
|
||||
|
||||
_device: XknxNotification
|
||||
|
||||
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
|
||||
"""Initialize a KNX notification."""
|
||||
super().__init__(_create_notification_instance(xknx, config))
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
async def async_send_message(self, message: str) -> None:
|
||||
"""Send a notification to knx bus."""
|
||||
await self._device.set(message)
|
||||
|
36
homeassistant/components/knx/repairs.py
Normal file
36
homeassistant/components/knx/repairs.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Repairs support for KNX."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@callback
|
||||
def migrate_notify_issue(hass: HomeAssistant) -> None:
|
||||
"""Create issue for notify service deprecation."""
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"migrate_notify",
|
||||
breaks_in_ha_version="2024.11.0",
|
||||
issue_domain=Platform.NOTIFY.value,
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
translation_key="migrate_notify",
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
)
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
data: dict[str, str | int | float | None] | None,
|
||||
) -> RepairsFlow:
|
||||
"""Create flow."""
|
||||
assert issue_id == "migrate_notify"
|
||||
return ConfirmRepairFlow()
|
@ -750,6 +750,7 @@ class NotifySchema(KNXPlatformSchema):
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_TYPE, default="latin_1"): string_type_validator,
|
||||
vol.Required(KNX_ADDRESS): ga_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -384,5 +384,18 @@
|
||||
"name": "[%key:common::action::reload%]",
|
||||
"description": "Reloads the KNX integration."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"migrate_notify": {
|
||||
"title": "Migration of KNX notify service",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "The KNX `notify` service has been migrated. New `notify` entities are available now.\n\nUpdate any automations to use the new `notify.send_message` exposed by these new entities. When this is done, fix this issue and restart Home Assistant.",
|
||||
"title": "Disable legacy KNX notify service"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Test KNX notify."""
|
||||
|
||||
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
|
||||
@ -8,7 +9,9 @@ from homeassistant.core import HomeAssistant
|
||||
from .conftest import KNXTestKit
|
||||
|
||||
|
||||
async def test_notify_simple(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
async def test_legacy_notify_service_simple(
|
||||
hass: HomeAssistant, knx: KNXTestKit
|
||||
) -> None:
|
||||
"""Test KNX notify can send to one device."""
|
||||
await knx.setup_integration(
|
||||
{
|
||||
@ -26,22 +29,7 @@ async def test_notify_simple(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
|
||||
await knx.assert_write(
|
||||
"1/0/0",
|
||||
(
|
||||
0x49,
|
||||
0x20,
|
||||
0x6C,
|
||||
0x6F,
|
||||
0x76,
|
||||
0x65,
|
||||
0x20,
|
||||
0x4B,
|
||||
0x4E,
|
||||
0x58,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
),
|
||||
(73, 32, 108, 111, 118, 101, 32, 75, 78, 88, 0, 0, 0, 0),
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -56,26 +44,11 @@ async def test_notify_simple(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
|
||||
await knx.assert_write(
|
||||
"1/0/0",
|
||||
(
|
||||
0x49,
|
||||
0x20,
|
||||
0x6C,
|
||||
0x6F,
|
||||
0x76,
|
||||
0x65,
|
||||
0x20,
|
||||
0x4B,
|
||||
0x4E,
|
||||
0x58,
|
||||
0x2C,
|
||||
0x20,
|
||||
0x62,
|
||||
0x75,
|
||||
),
|
||||
(73, 32, 108, 111, 118, 101, 32, 75, 78, 88, 44, 32, 98, 117),
|
||||
)
|
||||
|
||||
|
||||
async def test_notify_multiple_sends_to_all_with_different_encodings(
|
||||
async def test_legacy_notify_service_multiple_sends_to_all_with_different_encodings(
|
||||
hass: HomeAssistant, knx: KNXTestKit
|
||||
) -> None:
|
||||
"""Test KNX notify `type` configuration."""
|
||||
@ -110,3 +83,91 @@ async def test_notify_multiple_sends_to_all_with_different_encodings(
|
||||
"1/0/1",
|
||||
(71, 228, 110, 115, 101, 102, 252, 223, 99, 104, 101, 110, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
async def test_notify_simple(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
"""Test KNX notify can send to one device."""
|
||||
await knx.setup_integration(
|
||||
{
|
||||
NotifySchema.PLATFORM: {
|
||||
CONF_NAME: "test",
|
||||
KNX_ADDRESS: "1/0/0",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
notify.DOMAIN,
|
||||
notify.SERVICE_SEND_MESSAGE,
|
||||
{
|
||||
"entity_id": "notify.test",
|
||||
notify.ATTR_MESSAGE: "I love KNX",
|
||||
},
|
||||
)
|
||||
await knx.assert_write(
|
||||
"1/0/0",
|
||||
(73, 32, 108, 111, 118, 101, 32, 75, 78, 88, 0, 0, 0, 0),
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
notify.DOMAIN,
|
||||
notify.SERVICE_SEND_MESSAGE,
|
||||
{
|
||||
"entity_id": "notify.test",
|
||||
notify.ATTR_MESSAGE: "I love KNX, but this text is too long for KNX, poor KNX",
|
||||
},
|
||||
)
|
||||
await knx.assert_write(
|
||||
"1/0/0",
|
||||
(73, 32, 108, 111, 118, 101, 32, 75, 78, 88, 44, 32, 98, 117),
|
||||
)
|
||||
|
||||
|
||||
async def test_notify_multiple_sends_with_different_encodings(
|
||||
hass: HomeAssistant, knx: KNXTestKit
|
||||
) -> None:
|
||||
"""Test KNX notify `type` configuration."""
|
||||
await knx.setup_integration(
|
||||
{
|
||||
NotifySchema.PLATFORM: [
|
||||
{
|
||||
CONF_NAME: "ASCII",
|
||||
KNX_ADDRESS: "1/0/0",
|
||||
CONF_TYPE: "string",
|
||||
},
|
||||
{
|
||||
CONF_NAME: "Latin-1",
|
||||
KNX_ADDRESS: "1/0/1",
|
||||
CONF_TYPE: "latin_1",
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
message = {notify.ATTR_MESSAGE: "Gänsefüßchen"}
|
||||
|
||||
await hass.services.async_call(
|
||||
notify.DOMAIN,
|
||||
notify.SERVICE_SEND_MESSAGE,
|
||||
{
|
||||
"entity_id": "notify.ascii",
|
||||
**message,
|
||||
},
|
||||
)
|
||||
await knx.assert_write(
|
||||
"1/0/0",
|
||||
# "G?nsef??chen"
|
||||
(71, 63, 110, 115, 101, 102, 63, 63, 99, 104, 101, 110, 0, 0),
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
notify.DOMAIN,
|
||||
notify.SERVICE_SEND_MESSAGE,
|
||||
{
|
||||
"entity_id": "notify.latin_1",
|
||||
**message,
|
||||
},
|
||||
)
|
||||
await knx.assert_write(
|
||||
"1/0/1",
|
||||
(71, 228, 110, 115, 101, 102, 252, 223, 99, 104, 101, 110, 0, 0),
|
||||
)
|
||||
|
84
tests/components/knx/test_repairs.py
Normal file
84
tests/components/knx/test_repairs.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""Test repairs for KNX integration."""
|
||||
|
||||
from http import HTTPStatus
|
||||
|
||||
from homeassistant.components.knx.const import DOMAIN, KNX_ADDRESS
|
||||
from homeassistant.components.knx.schema import NotifySchema
|
||||
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
|
||||
from homeassistant.components.repairs.websocket_api import (
|
||||
RepairsFlowIndexView,
|
||||
RepairsFlowResourceView,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
|
||||
from .conftest import KNXTestKit
|
||||
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
async def test_knx_notify_service_issue(
|
||||
hass: HomeAssistant,
|
||||
knx: KNXTestKit,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test the legacy notify service still works before migration and repair flow is triggered."""
|
||||
await knx.setup_integration(
|
||||
{
|
||||
NotifySchema.PLATFORM: {
|
||||
CONF_NAME: "test",
|
||||
KNX_ADDRESS: "1/0/0",
|
||||
}
|
||||
}
|
||||
)
|
||||
http_client = await hass_client()
|
||||
|
||||
# Assert no issue is present
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
# Simulate legacy service being used
|
||||
assert hass.services.has_service(NOTIFY_DOMAIN, NOTIFY_DOMAIN)
|
||||
await hass.services.async_call(
|
||||
NOTIFY_DOMAIN,
|
||||
NOTIFY_DOMAIN,
|
||||
service_data={"message": "It is too cold!", "target": "test"},
|
||||
blocking=True,
|
||||
)
|
||||
await knx.assert_write(
|
||||
"1/0/0",
|
||||
(73, 116, 32, 105, 115, 32, 116, 111, 111, 32, 99, 111, 108, 100),
|
||||
)
|
||||
|
||||
# Assert the issue is present
|
||||
assert len(issue_registry.issues) == 1
|
||||
assert issue_registry.async_get_issue(
|
||||
domain=DOMAIN,
|
||||
issue_id="migrate_notify",
|
||||
)
|
||||
|
||||
# Test confirm step in repair flow
|
||||
resp = await http_client.post(
|
||||
RepairsFlowIndexView.url,
|
||||
json={"handler": DOMAIN, "issue_id": "migrate_notify"},
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
resp = await http_client.post(
|
||||
RepairsFlowResourceView.url.format(flow_id=flow_id),
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
assert data["type"] == "create_entry"
|
||||
|
||||
# Assert the issue is no longer present
|
||||
assert not issue_registry.async_get_issue(
|
||||
domain=DOMAIN,
|
||||
issue_id="migrate_notify",
|
||||
)
|
||||
assert len(issue_registry.issues) == 0
|
Loading…
x
Reference in New Issue
Block a user