mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
This reverts commit e574a3ef1d855304b2a78c389861c421b1548d74.
This commit is contained in:
parent
d17f8e9ed6
commit
5afe8fd2db
@ -160,7 +160,6 @@ PLATFORMS = [
|
|||||||
Platform.HUMIDIFIER,
|
Platform.HUMIDIFIER,
|
||||||
Platform.LIGHT,
|
Platform.LIGHT,
|
||||||
Platform.LOCK,
|
Platform.LOCK,
|
||||||
Platform.NOTIFY,
|
|
||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
Platform.SELECT,
|
Platform.SELECT,
|
||||||
Platform.SCENE,
|
Platform.SCENE,
|
||||||
|
@ -185,8 +185,6 @@ ABBREVIATIONS = {
|
|||||||
"set_fan_spd_t": "set_fan_speed_topic",
|
"set_fan_spd_t": "set_fan_speed_topic",
|
||||||
"set_pos_tpl": "set_position_template",
|
"set_pos_tpl": "set_position_template",
|
||||||
"set_pos_t": "set_position_topic",
|
"set_pos_t": "set_position_topic",
|
||||||
"title": "title",
|
|
||||||
"trgts": "targets",
|
|
||||||
"pos_t": "position_topic",
|
"pos_t": "position_topic",
|
||||||
"pos_tpl": "position_template",
|
"pos_tpl": "position_template",
|
||||||
"spd_cmd_t": "speed_command_topic",
|
"spd_cmd_t": "speed_command_topic",
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""Constants used by multiple MQTT modules."""
|
"""Constants used by multiple MQTT modules."""
|
||||||
from typing import Final
|
|
||||||
|
|
||||||
from homeassistant.const import CONF_PAYLOAD
|
from homeassistant.const import CONF_PAYLOAD
|
||||||
|
|
||||||
ATTR_DISCOVERY_HASH = "discovery_hash"
|
ATTR_DISCOVERY_HASH = "discovery_hash"
|
||||||
@ -14,11 +12,11 @@ ATTR_TOPIC = "topic"
|
|||||||
CONF_AVAILABILITY = "availability"
|
CONF_AVAILABILITY = "availability"
|
||||||
CONF_BROKER = "broker"
|
CONF_BROKER = "broker"
|
||||||
CONF_BIRTH_MESSAGE = "birth_message"
|
CONF_BIRTH_MESSAGE = "birth_message"
|
||||||
CONF_COMMAND_TEMPLATE: Final = "command_template"
|
CONF_COMMAND_TEMPLATE = "command_template"
|
||||||
CONF_COMMAND_TOPIC: Final = "command_topic"
|
CONF_COMMAND_TOPIC = "command_topic"
|
||||||
CONF_ENCODING: Final = "encoding"
|
CONF_ENCODING = "encoding"
|
||||||
CONF_QOS: Final = "qos"
|
CONF_QOS = ATTR_QOS
|
||||||
CONF_RETAIN: Final = "retain"
|
CONF_RETAIN = ATTR_RETAIN
|
||||||
CONF_STATE_TOPIC = "state_topic"
|
CONF_STATE_TOPIC = "state_topic"
|
||||||
CONF_STATE_VALUE_TEMPLATE = "state_value_template"
|
CONF_STATE_VALUE_TEMPLATE = "state_value_template"
|
||||||
CONF_TOPIC = "topic"
|
CONF_TOPIC = "topic"
|
||||||
|
@ -15,7 +15,6 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
||||||
from homeassistant.loader import async_get_mqtt
|
from homeassistant.loader import async_get_mqtt
|
||||||
|
|
||||||
from .. import mqtt
|
from .. import mqtt
|
||||||
@ -49,7 +48,6 @@ SUPPORTED_COMPONENTS = [
|
|||||||
"humidifier",
|
"humidifier",
|
||||||
"light",
|
"light",
|
||||||
"lock",
|
"lock",
|
||||||
"notify",
|
|
||||||
"number",
|
"number",
|
||||||
"scene",
|
"scene",
|
||||||
"siren",
|
"siren",
|
||||||
@ -234,15 +232,7 @@ async def async_start( # noqa: C901
|
|||||||
from . import device_automation
|
from . import device_automation
|
||||||
|
|
||||||
await device_automation.async_setup_entry(hass, config_entry)
|
await device_automation.async_setup_entry(hass, config_entry)
|
||||||
elif component in "notify":
|
elif component == "tag":
|
||||||
# Local import to avoid circular dependencies
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from . import notify
|
|
||||||
|
|
||||||
await notify.async_setup_entry(
|
|
||||||
hass, config_entry, AddEntitiesCallback
|
|
||||||
)
|
|
||||||
elif component in "tag":
|
|
||||||
# Local import to avoid circular dependencies
|
# Local import to avoid circular dependencies
|
||||||
# pylint: disable-next=import-outside-toplevel
|
# pylint: disable-next=import-outside-toplevel
|
||||||
from . import tag
|
from . import tag
|
||||||
|
@ -5,7 +5,7 @@ from abc import abstractmethod
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Protocol, cast
|
from typing import Any, Protocol
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -237,10 +237,10 @@ class SetupEntity(Protocol):
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry_helper(hass, domain, async_setup, schema):
|
async def async_setup_entry_helper(hass, domain, async_setup, schema):
|
||||||
"""Set up entity, automation, notify service or tag creation dynamically through MQTT discovery."""
|
"""Set up entity, automation or tag creation dynamically through MQTT discovery."""
|
||||||
|
|
||||||
async def async_discover(discovery_payload):
|
async def async_discover(discovery_payload):
|
||||||
"""Discover and add an MQTT entity, automation, notify service or tag."""
|
"""Discover and add an MQTT entity, automation or tag."""
|
||||||
discovery_data = discovery_payload.discovery_data
|
discovery_data = discovery_payload.discovery_data
|
||||||
try:
|
try:
|
||||||
config = schema(discovery_payload)
|
config = schema(discovery_payload)
|
||||||
@ -496,13 +496,11 @@ class MqttAvailability(Entity):
|
|||||||
return self._available_latest
|
return self._available_latest
|
||||||
|
|
||||||
|
|
||||||
async def cleanup_device_registry(
|
async def cleanup_device_registry(hass, device_id, config_entry_id):
|
||||||
hass: HomeAssistant, device_id: str | None, config_entry_id: str | None
|
"""Remove device registry entry if there are no remaining entities or triggers."""
|
||||||
) -> None:
|
|
||||||
"""Remove device registry entry if there are no remaining entities, triggers or notify services."""
|
|
||||||
# Local import to avoid circular dependencies
|
# Local import to avoid circular dependencies
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable-next=import-outside-toplevel
|
||||||
from . import device_trigger, notify, tag
|
from . import device_trigger, tag
|
||||||
|
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
@ -513,10 +511,9 @@ async def cleanup_device_registry(
|
|||||||
)
|
)
|
||||||
and not await device_trigger.async_get_triggers(hass, device_id)
|
and not await device_trigger.async_get_triggers(hass, device_id)
|
||||||
and not tag.async_has_tags(hass, device_id)
|
and not tag.async_has_tags(hass, device_id)
|
||||||
and not notify.device_has_notify_services(hass, device_id)
|
|
||||||
):
|
):
|
||||||
device_registry.async_update_device(
|
device_registry.async_update_device(
|
||||||
device_id, remove_config_entry_id=cast(str, config_entry_id)
|
device_id, remove_config_entry_id=config_entry_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,406 +0,0 @@
|
|||||||
"""Support for MQTT notify."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import logging
|
|
||||||
from typing import Any, Final, TypedDict, cast
|
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components import notify
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_DEVICE, CONF_NAME
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.helpers import device_registry as dr
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
|
||||||
from homeassistant.helpers.dispatcher import (
|
|
||||||
async_dispatcher_connect,
|
|
||||||
async_dispatcher_send,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
||||||
from homeassistant.helpers.reload import async_setup_reload_service
|
|
||||||
from homeassistant.helpers.template import Template
|
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
from homeassistant.util import slugify
|
|
||||||
|
|
||||||
from . import PLATFORMS, MqttCommandTemplate
|
|
||||||
from .. import mqtt
|
|
||||||
from .const import (
|
|
||||||
ATTR_DISCOVERY_HASH,
|
|
||||||
ATTR_DISCOVERY_PAYLOAD,
|
|
||||||
CONF_COMMAND_TEMPLATE,
|
|
||||||
CONF_COMMAND_TOPIC,
|
|
||||||
CONF_ENCODING,
|
|
||||||
CONF_QOS,
|
|
||||||
CONF_RETAIN,
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_UPDATED, clear_discovery_hash
|
|
||||||
from .mixins import (
|
|
||||||
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
|
||||||
async_setup_entry_helper,
|
|
||||||
cleanup_device_registry,
|
|
||||||
device_info_from_config,
|
|
||||||
)
|
|
||||||
|
|
||||||
CONF_TARGETS: Final = "targets"
|
|
||||||
CONF_TITLE: Final = "title"
|
|
||||||
CONF_CONFIG_ENTRY: Final = "config_entry"
|
|
||||||
CONF_DISCOVER_HASH: Final = "discovery_hash"
|
|
||||||
|
|
||||||
MQTT_NOTIFY_SERVICES_SETUP = "mqtt_notify_services_setup"
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
|
||||||
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
|
|
||||||
vol.Required(CONF_NAME): cv.string,
|
|
||||||
vol.Optional(CONF_TARGETS, default=[]): cv.ensure_list,
|
|
||||||
vol.Optional(CONF_TITLE, default=notify.ATTR_TITLE_DEFAULT): cv.string,
|
|
||||||
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
|
||||||
},
|
|
||||||
extra=vol.REMOVE_EXTRA,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class MqttNotificationConfig(TypedDict, total=False):
|
|
||||||
"""Supply service parameters for MqttNotificationService."""
|
|
||||||
|
|
||||||
command_topic: str
|
|
||||||
command_template: Template
|
|
||||||
encoding: str
|
|
||||||
name: str | None
|
|
||||||
qos: int
|
|
||||||
retain: bool
|
|
||||||
targets: list
|
|
||||||
title: str
|
|
||||||
device: ConfigType
|
|
||||||
|
|
||||||
|
|
||||||
async def async_initialize(hass: HomeAssistant) -> None:
|
|
||||||
"""Initialize globals."""
|
|
||||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
|
||||||
hass.data.setdefault(MQTT_NOTIFY_SERVICES_SETUP, {})
|
|
||||||
|
|
||||||
|
|
||||||
def device_has_notify_services(hass: HomeAssistant, device_id: str) -> bool:
|
|
||||||
"""Check if the device has registered notify services."""
|
|
||||||
if MQTT_NOTIFY_SERVICES_SETUP not in hass.data:
|
|
||||||
return False
|
|
||||||
for key, service in hass.data[ # pylint: disable=unused-variable
|
|
||||||
MQTT_NOTIFY_SERVICES_SETUP
|
|
||||||
].items():
|
|
||||||
if service.device_id == device_id:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _check_notify_service_name(
|
|
||||||
hass: HomeAssistant, config: MqttNotificationConfig
|
|
||||||
) -> str | None:
|
|
||||||
"""Check if the service already exists or else return the service name."""
|
|
||||||
service_name = slugify(config[CONF_NAME])
|
|
||||||
has_services = hass.services.has_service(notify.DOMAIN, service_name)
|
|
||||||
services = hass.data[MQTT_NOTIFY_SERVICES_SETUP]
|
|
||||||
if service_name in services.keys() or has_services:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Notify service '%s' already exists, cannot register service",
|
|
||||||
service_name,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
return service_name
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config_entry: ConfigEntry,
|
|
||||||
async_add_entities: AddEntitiesCallback,
|
|
||||||
) -> None:
|
|
||||||
"""Set up MQTT notify service dynamically through MQTT discovery."""
|
|
||||||
await async_initialize(hass)
|
|
||||||
setup = functools.partial(_async_setup_notify, hass, config_entry=config_entry)
|
|
||||||
await async_setup_entry_helper(hass, notify.DOMAIN, setup, DISCOVERY_SCHEMA)
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_setup_notify(
|
|
||||||
hass,
|
|
||||||
legacy_config: ConfigType,
|
|
||||||
config_entry: ConfigEntry,
|
|
||||||
discovery_data: dict[str, Any],
|
|
||||||
):
|
|
||||||
"""Set up the MQTT notify service with auto discovery."""
|
|
||||||
config: MqttNotificationConfig = DISCOVERY_SCHEMA(
|
|
||||||
discovery_data[ATTR_DISCOVERY_PAYLOAD]
|
|
||||||
)
|
|
||||||
discovery_hash = discovery_data[ATTR_DISCOVERY_HASH]
|
|
||||||
|
|
||||||
if not (service_name := _check_notify_service_name(hass, config)):
|
|
||||||
async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None)
|
|
||||||
clear_discovery_hash(hass, discovery_hash)
|
|
||||||
return
|
|
||||||
|
|
||||||
device_id = _update_device(hass, config_entry, config)
|
|
||||||
|
|
||||||
service = MqttNotificationService(
|
|
||||||
hass,
|
|
||||||
config,
|
|
||||||
config_entry,
|
|
||||||
device_id,
|
|
||||||
discovery_hash,
|
|
||||||
)
|
|
||||||
hass.data[MQTT_NOTIFY_SERVICES_SETUP][service_name] = service
|
|
||||||
|
|
||||||
await service.async_setup(hass, service_name, service_name)
|
|
||||||
await service.async_register_services()
|
|
||||||
|
|
||||||
|
|
||||||
async def async_get_service(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config: ConfigType,
|
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> MqttNotificationService | None:
|
|
||||||
"""Prepare the MQTT notification service through configuration.yaml."""
|
|
||||||
await async_initialize(hass)
|
|
||||||
notification_config: MqttNotificationConfig = cast(MqttNotificationConfig, config)
|
|
||||||
|
|
||||||
if not (service_name := _check_notify_service_name(hass, notification_config)):
|
|
||||||
return None
|
|
||||||
|
|
||||||
service = hass.data[MQTT_NOTIFY_SERVICES_SETUP][
|
|
||||||
service_name
|
|
||||||
] = MqttNotificationService(
|
|
||||||
hass,
|
|
||||||
notification_config,
|
|
||||||
)
|
|
||||||
return service
|
|
||||||
|
|
||||||
|
|
||||||
class MqttNotificationServiceUpdater:
|
|
||||||
"""Add support for auto discovery updates."""
|
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, service: MqttNotificationService) -> None:
|
|
||||||
"""Initialize the update service."""
|
|
||||||
|
|
||||||
async def async_discovery_update(
|
|
||||||
discovery_payload: DiscoveryInfoType | None,
|
|
||||||
) -> None:
|
|
||||||
"""Handle discovery update."""
|
|
||||||
if not discovery_payload:
|
|
||||||
# unregister notify service through auto discovery
|
|
||||||
async_dispatcher_send(
|
|
||||||
hass, MQTT_DISCOVERY_DONE.format(service.discovery_hash), None
|
|
||||||
)
|
|
||||||
await async_tear_down_service()
|
|
||||||
return
|
|
||||||
|
|
||||||
# update notify service through auto discovery
|
|
||||||
await service.async_update_service(discovery_payload)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Notify service %s updated has been processed",
|
|
||||||
service.discovery_hash,
|
|
||||||
)
|
|
||||||
async_dispatcher_send(
|
|
||||||
hass, MQTT_DISCOVERY_DONE.format(service.discovery_hash), None
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_device_removed(event):
|
|
||||||
"""Handle the removal of a device."""
|
|
||||||
device_id = event.data["device_id"]
|
|
||||||
if (
|
|
||||||
event.data["action"] != "remove"
|
|
||||||
or device_id != service.device_id
|
|
||||||
or self._device_removed
|
|
||||||
):
|
|
||||||
return
|
|
||||||
self._device_removed = True
|
|
||||||
await async_tear_down_service()
|
|
||||||
|
|
||||||
async def async_tear_down_service():
|
|
||||||
"""Handle the removal of the service."""
|
|
||||||
services = hass.data[MQTT_NOTIFY_SERVICES_SETUP]
|
|
||||||
if self._service.service_name in services.keys():
|
|
||||||
del services[self._service.service_name]
|
|
||||||
if not self._device_removed and service.config_entry:
|
|
||||||
self._device_removed = True
|
|
||||||
await cleanup_device_registry(
|
|
||||||
hass, service.device_id, service.config_entry.entry_id
|
|
||||||
)
|
|
||||||
clear_discovery_hash(hass, service.discovery_hash)
|
|
||||||
self._remove_discovery()
|
|
||||||
await service.async_unregister_services()
|
|
||||||
_LOGGER.info(
|
|
||||||
"Notify service %s has been removed",
|
|
||||||
service.discovery_hash,
|
|
||||||
)
|
|
||||||
del self._service
|
|
||||||
|
|
||||||
self._service = service
|
|
||||||
self._remove_discovery = async_dispatcher_connect(
|
|
||||||
hass,
|
|
||||||
MQTT_DISCOVERY_UPDATED.format(service.discovery_hash),
|
|
||||||
async_discovery_update,
|
|
||||||
)
|
|
||||||
if service.device_id:
|
|
||||||
self._remove_device_updated = hass.bus.async_listen(
|
|
||||||
EVENT_DEVICE_REGISTRY_UPDATED, async_device_removed
|
|
||||||
)
|
|
||||||
self._device_removed = False
|
|
||||||
async_dispatcher_send(
|
|
||||||
hass, MQTT_DISCOVERY_DONE.format(service.discovery_hash), None
|
|
||||||
)
|
|
||||||
_LOGGER.info(
|
|
||||||
"Notify service %s has been initialized",
|
|
||||||
service.discovery_hash,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MqttNotificationService(notify.BaseNotificationService):
|
|
||||||
"""Implement the notification service for MQTT."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
service_config: MqttNotificationConfig,
|
|
||||||
config_entry: ConfigEntry | None = None,
|
|
||||||
device_id: str | None = None,
|
|
||||||
discovery_hash: tuple | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the service."""
|
|
||||||
self.hass = hass
|
|
||||||
self._config = service_config
|
|
||||||
self._commmand_template = MqttCommandTemplate(
|
|
||||||
service_config.get(CONF_COMMAND_TEMPLATE), hass=hass
|
|
||||||
)
|
|
||||||
self._device_id = device_id
|
|
||||||
self._discovery_hash = discovery_hash
|
|
||||||
self._config_entry = config_entry
|
|
||||||
self._service_name = slugify(service_config[CONF_NAME])
|
|
||||||
|
|
||||||
self._updater = (
|
|
||||||
MqttNotificationServiceUpdater(hass, self) if discovery_hash else None
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_id(self) -> str | None:
|
|
||||||
"""Return the device ID."""
|
|
||||||
return self._device_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config_entry(self) -> ConfigEntry | None:
|
|
||||||
"""Return the config_entry."""
|
|
||||||
return self._config_entry
|
|
||||||
|
|
||||||
@property
|
|
||||||
def discovery_hash(self) -> tuple | None:
|
|
||||||
"""Return the discovery hash."""
|
|
||||||
return self._discovery_hash
|
|
||||||
|
|
||||||
@property
|
|
||||||
def service_name(self) -> str:
|
|
||||||
"""Return the service ma,e."""
|
|
||||||
return self._service_name
|
|
||||||
|
|
||||||
async def async_update_service(
|
|
||||||
self,
|
|
||||||
discovery_payload: DiscoveryInfoType,
|
|
||||||
) -> None:
|
|
||||||
"""Update the notify service through auto discovery."""
|
|
||||||
config: MqttNotificationConfig = DISCOVERY_SCHEMA(discovery_payload)
|
|
||||||
# Do not rename a service if that service_name is already in use
|
|
||||||
if (
|
|
||||||
new_service_name := slugify(config[CONF_NAME])
|
|
||||||
) != self._service_name and _check_notify_service_name(
|
|
||||||
self.hass, config
|
|
||||||
) is None:
|
|
||||||
return
|
|
||||||
# Only refresh services if service name or targets have changes
|
|
||||||
if (
|
|
||||||
new_service_name != self._service_name
|
|
||||||
or config[CONF_TARGETS] != self._config[CONF_TARGETS]
|
|
||||||
):
|
|
||||||
services = self.hass.data[MQTT_NOTIFY_SERVICES_SETUP]
|
|
||||||
await self.async_unregister_services()
|
|
||||||
if self._service_name in services:
|
|
||||||
del services[self._service_name]
|
|
||||||
self._config = config
|
|
||||||
self._service_name = new_service_name
|
|
||||||
await self.async_register_services()
|
|
||||||
services[new_service_name] = self
|
|
||||||
else:
|
|
||||||
self._config = config
|
|
||||||
self._commmand_template = MqttCommandTemplate(
|
|
||||||
config.get(CONF_COMMAND_TEMPLATE), hass=self.hass
|
|
||||||
)
|
|
||||||
_update_device(self.hass, self._config_entry, config)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def targets(self) -> dict[str, str]:
|
|
||||||
"""Return a dictionary of registered targets."""
|
|
||||||
return {target: target for target in self._config[CONF_TARGETS]}
|
|
||||||
|
|
||||||
async def async_send_message(self, message: str = "", **kwargs):
|
|
||||||
"""Build and send a MQTT message."""
|
|
||||||
target = kwargs.get(notify.ATTR_TARGET)
|
|
||||||
if (
|
|
||||||
target is not None
|
|
||||||
and self._config[CONF_TARGETS]
|
|
||||||
and set(target) & set(self._config[CONF_TARGETS]) != set(target)
|
|
||||||
):
|
|
||||||
_LOGGER.error(
|
|
||||||
"Cannot send %s, target list %s is invalid, valid available targets: %s",
|
|
||||||
message,
|
|
||||||
target,
|
|
||||||
self._config[CONF_TARGETS],
|
|
||||||
)
|
|
||||||
return
|
|
||||||
variables = {
|
|
||||||
"message": message,
|
|
||||||
"name": self._config[CONF_NAME],
|
|
||||||
"service": self._service_name,
|
|
||||||
"target": target or self._config[CONF_TARGETS],
|
|
||||||
"title": kwargs.get(notify.ATTR_TITLE, self._config[CONF_TITLE]),
|
|
||||||
}
|
|
||||||
variables.update(kwargs.get(notify.ATTR_DATA) or {})
|
|
||||||
payload = self._commmand_template.async_render(
|
|
||||||
message,
|
|
||||||
variables=variables,
|
|
||||||
)
|
|
||||||
await mqtt.async_publish(
|
|
||||||
self.hass,
|
|
||||||
self._config[CONF_COMMAND_TOPIC],
|
|
||||||
payload,
|
|
||||||
self._config[CONF_QOS],
|
|
||||||
self._config[CONF_RETAIN],
|
|
||||||
self._config[CONF_ENCODING],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _update_device(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config_entry: ConfigEntry | None,
|
|
||||||
config: MqttNotificationConfig,
|
|
||||||
) -> str | None:
|
|
||||||
"""Update device registry."""
|
|
||||||
if config_entry is None or CONF_DEVICE not in config:
|
|
||||||
return None
|
|
||||||
|
|
||||||
device = None
|
|
||||||
device_registry = dr.async_get(hass)
|
|
||||||
config_entry_id = config_entry.entry_id
|
|
||||||
device_info = device_info_from_config(config[CONF_DEVICE])
|
|
||||||
|
|
||||||
if config_entry_id is not None and device_info is not None:
|
|
||||||
update_device_info = cast(dict, device_info)
|
|
||||||
update_device_info["config_entry_id"] = config_entry_id
|
|
||||||
device = device_registry.async_get_or_create(**update_device_info)
|
|
||||||
|
|
||||||
return device.id if device else None
|
|
@ -1,863 +0,0 @@
|
|||||||
"""The tests for the MQTT button platform."""
|
|
||||||
import copy
|
|
||||||
import json
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from homeassistant import config as hass_config
|
|
||||||
from homeassistant.components import notify
|
|
||||||
from homeassistant.components.mqtt import DOMAIN
|
|
||||||
from homeassistant.const import CONF_NAME, SERVICE_RELOAD
|
|
||||||
from homeassistant.exceptions import ServiceNotFound
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
from homeassistant.util import slugify
|
|
||||||
|
|
||||||
from tests.common import async_fire_mqtt_message, mock_device_registry
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = {notify.DOMAIN: {"platform": "mqtt", "command_topic": "test-topic"}}
|
|
||||||
|
|
||||||
COMMAND_TEMPLATE_TEST_PARAMS = (
|
|
||||||
"name,service,parameters,expected_result",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"My service",
|
|
||||||
"my_service",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_DATA: {"par1": "val1"},
|
|
||||||
},
|
|
||||||
'{"message":"Message",'
|
|
||||||
'"name":"My service",'
|
|
||||||
'"service":"my_service",'
|
|
||||||
'"par1":"val1",'
|
|
||||||
'"target":['
|
|
||||||
"'t1', 't2'"
|
|
||||||
"],"
|
|
||||||
'"title":"Title"}',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"My service",
|
|
||||||
"my_service",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_DATA: {"par1": "val1"},
|
|
||||||
notify.ATTR_TARGET: ["t2"],
|
|
||||||
},
|
|
||||||
'{"message":"Message",'
|
|
||||||
'"name":"My service",'
|
|
||||||
'"service":"my_service",'
|
|
||||||
'"par1":"val1",'
|
|
||||||
'"target":['
|
|
||||||
"'t2'"
|
|
||||||
"],"
|
|
||||||
'"title":"Title"}',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"My service",
|
|
||||||
"my_service_t1",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title2",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_DATA: {"par1": "val2"},
|
|
||||||
},
|
|
||||||
'{"message":"Message",'
|
|
||||||
'"name":"My service",'
|
|
||||||
'"service":"my_service",'
|
|
||||||
'"par1":"val2",'
|
|
||||||
'"target":['
|
|
||||||
"'t1'"
|
|
||||||
"],"
|
|
||||||
'"title":"Title2"}',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def device_reg(hass):
|
|
||||||
"""Return an empty, loaded, registry."""
|
|
||||||
return mock_device_registry(hass)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_notifify_service_with_auto_discovery(
|
|
||||||
hass, mqtt_mock, caplog, device_reg, data, service_name
|
|
||||||
):
|
|
||||||
"""Test setup notify service with a device config."""
|
|
||||||
caplog.clear()
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/{service_name}/config", data
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is not None
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_name}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_name}_target1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_name}_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(*COMMAND_TEMPLATE_TEST_PARAMS)
|
|
||||||
async def test_sending_with_command_templates_with_config_setup(
|
|
||||||
hass, mqtt_mock, caplog, name, service, parameters, expected_result
|
|
||||||
):
|
|
||||||
"""Test the sending MQTT commands using a template using config setup."""
|
|
||||||
config = {
|
|
||||||
"name": name,
|
|
||||||
"command_topic": "lcd/set",
|
|
||||||
"command_template": "{"
|
|
||||||
'"message":"{{message}}",'
|
|
||||||
'"name":"{{name}}",'
|
|
||||||
'"service":"{{service}}",'
|
|
||||||
'"par1":"{{par1}}",'
|
|
||||||
'"target":{{target}},'
|
|
||||||
'"title":"{{title}}"'
|
|
||||||
"}",
|
|
||||||
"targets": ["t1", "t2"],
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "1",
|
|
||||||
}
|
|
||||||
service_base_name = slugify(name)
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
notify.DOMAIN,
|
|
||||||
{notify.DOMAIN: config},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_base_name}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_base_name}_t1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_base_name}_t2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
service,
|
|
||||||
parameters,
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"lcd/set", expected_result, 1, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(*COMMAND_TEMPLATE_TEST_PARAMS)
|
|
||||||
async def test_sending_with_command_templates_auto_discovery(
|
|
||||||
hass, mqtt_mock, caplog, name, service, parameters, expected_result
|
|
||||||
):
|
|
||||||
"""Test the sending MQTT commands using a template and auto discovery."""
|
|
||||||
config = {
|
|
||||||
"name": name,
|
|
||||||
"command_topic": "lcd/set",
|
|
||||||
"command_template": "{"
|
|
||||||
'"message":"{{message}}",'
|
|
||||||
'"name":"{{name}}",'
|
|
||||||
'"service":"{{service}}",'
|
|
||||||
'"par1":"{{par1}}",'
|
|
||||||
'"target":{{target}},'
|
|
||||||
'"title":"{{title}}"'
|
|
||||||
"}",
|
|
||||||
"targets": ["t1", "t2"],
|
|
||||||
"qos": "1",
|
|
||||||
}
|
|
||||||
if name:
|
|
||||||
config[CONF_NAME] = name
|
|
||||||
service_base_name = slugify(name)
|
|
||||||
else:
|
|
||||||
service_base_name = DOMAIN
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/bla/config", json.dumps(config)
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_base_name}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_base_name}_t1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_registered[L]: domain=notify, service={service_base_name}_t2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
service,
|
|
||||||
parameters,
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"lcd/set", expected_result, 1, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_sending_mqtt_commands(hass, mqtt_mock, caplog):
|
|
||||||
"""Test the sending MQTT commands."""
|
|
||||||
config1 = {
|
|
||||||
"command_topic": "command-topic1",
|
|
||||||
"name": "test1",
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "2",
|
|
||||||
}
|
|
||||||
config2 = {
|
|
||||||
"command_topic": "command-topic2",
|
|
||||||
"name": "test2",
|
|
||||||
"targets": ["t1", "t2"],
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "2",
|
|
||||||
}
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
notify.DOMAIN,
|
|
||||||
{notify.DOMAIN: [config1, config2]},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert "<Event service_registered[L]: domain=notify, service=test1>" in caplog.text
|
|
||||||
assert "<Event service_registered[L]: domain=notify, service=test2>" in caplog.text
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test2_t1>" in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test2_t2>" in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
# test1 simple call without targets
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test1",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic1", "Message", 2, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
# test2 simple call without targets
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test2",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic2", "Message", 2, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
# test2 simple call main service without target
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test2",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic2", "Message", 2, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
# test2 simple call main service with empty target
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test2",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_TARGET: [],
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic2", "Message", 2, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
# test2 simple call main service with single target
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test2",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_TARGET: ["t1"],
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic2", "Message", 2, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
# test2 simple call main service with invalid target
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test2",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_TARGET: ["invalid"],
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"Cannot send Message, target list ['invalid'] is invalid, valid available targets: ['t1', 't2']"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.call_count == 0
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_with_same_name(hass, mqtt_mock, caplog):
|
|
||||||
"""Test the multiple setups with the same name."""
|
|
||||||
config1 = {
|
|
||||||
"command_topic": "command-topic1",
|
|
||||||
"name": "test_same_name",
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "2",
|
|
||||||
}
|
|
||||||
config2 = {
|
|
||||||
"command_topic": "command-topic2",
|
|
||||||
"name": "test_same_name",
|
|
||||||
"targets": ["t1", "t2"],
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "2",
|
|
||||||
}
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
notify.DOMAIN,
|
|
||||||
{notify.DOMAIN: [config1, config2]},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_same_name>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"Notify service 'test_same_name' already exists, cannot register service"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
# test call main service on service with multiple targets with the same name
|
|
||||||
# the first configured service should publish
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test_same_name",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic1", "Message", 2, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
with pytest.raises(ServiceNotFound):
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test_same_name_t2",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_TARGET: ["t2"],
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_without_device(hass, mqtt_mock, caplog):
|
|
||||||
"""Test discovery, update and removal of notify service without device."""
|
|
||||||
data = '{ "name": "Old name", "command_topic": "test_topic" }'
|
|
||||||
data_update = '{ "command_topic": "test_topic_update", "name": "New name" }'
|
|
||||||
data_update_with_targets1 = '{ "command_topic": "test_topic", "name": "My notify service", "targets": ["target1", "target2"] }'
|
|
||||||
data_update_with_targets2 = '{ "command_topic": "test_topic", "name": "My notify service", "targets": ["target1", "target3"] }'
|
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=old_name>" in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"old_name",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with("test_topic", "Message", 0, False)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/bla/config", data_update
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert "<Event service_removed[L]: domain=notify, service=old_name>" in caplog.text
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=new_name>" in caplog.text
|
|
||||||
)
|
|
||||||
assert "Notify service ('notify', 'bla') updated has been processed" in caplog.text
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"new_name",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"test_topic_update", "Message", 0, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", "")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert "<Event service_removed[L]: domain=notify, service=new_name>" in caplog.text
|
|
||||||
|
|
||||||
# rediscover with targets
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/bla/config", data_update_with_targets1
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=my_notify_service>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=my_notify_service_target1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=my_notify_service_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
# update available targets
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/bla/config", data_update_with_targets2
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"<Event service_removed[L]: domain=notify, service=my_notify_service_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=my_notify_service_target3>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
# test if a new service with same name fails to setup
|
|
||||||
config1 = {
|
|
||||||
"command_topic": "command-topic-config.yaml",
|
|
||||||
"name": "test-setup1",
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "2",
|
|
||||||
}
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
notify.DOMAIN,
|
|
||||||
{notify.DOMAIN: [config1]},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
data = '{ "name": "test-setup1", "command_topic": "test_topic" }'
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/test-setup1/config", data
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
"Notify service 'test_setup1' already exists, cannot register service"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test_setup1",
|
|
||||||
{
|
|
||||||
notify.ATTR_TITLE: "Title",
|
|
||||||
notify.ATTR_MESSAGE: "Message",
|
|
||||||
notify.ATTR_TARGET: ["t2"],
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"command-topic-config.yaml", "Message", 2, False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test with same discovery on new name
|
|
||||||
data = '{ "name": "testa", "command_topic": "test_topic_a" }'
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/testa/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert "<Event service_registered[L]: domain=notify, service=testa>" in caplog.text
|
|
||||||
|
|
||||||
data = '{ "name": "testb", "command_topic": "test_topic_b" }'
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/testb/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert "<Event service_registered[L]: domain=notify, service=testb>" in caplog.text
|
|
||||||
|
|
||||||
# Try to update from new discovery of existing service test
|
|
||||||
data = '{ "name": "testa", "command_topic": "test_topic_c" }'
|
|
||||||
caplog.clear()
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/testc/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
"Notify service 'testa' already exists, cannot register service" in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
# Try to update the same discovery to existing service test
|
|
||||||
data = '{ "name": "testa", "command_topic": "test_topic_c" }'
|
|
||||||
caplog.clear()
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/testb/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
"Notify service 'testa' already exists, cannot register service" in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_with_device_update(hass, mqtt_mock, caplog, device_reg):
|
|
||||||
"""Test discovery, update and removal of notify service with a device config."""
|
|
||||||
|
|
||||||
# Initial setup
|
|
||||||
data = '{ "command_topic": "test_topic", "name": "My notify service", "targets": ["target1", "target2"], "device":{"identifiers":["LCD_61236812_ADBA"], "name": "Test123" } }'
|
|
||||||
service_name = "my_notify_service"
|
|
||||||
await async_setup_notifify_service_with_auto_discovery(
|
|
||||||
hass, mqtt_mock, caplog, device_reg, data, service_name
|
|
||||||
)
|
|
||||||
assert "<Event device_registry_updated[L]: action=create, device_id=" in caplog.text
|
|
||||||
# Test device update
|
|
||||||
data_device_update = '{ "command_topic": "test_topic", "name": "My notify service", "targets": ["target1", "target2"], "device":{"identifiers":["LCD_61236812_ADBA"], "name": "Name update" } }'
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/{service_name}/config", data_device_update
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is not None
|
|
||||||
device_id = device_entry.id
|
|
||||||
assert device_id == device_entry.id
|
|
||||||
assert device_entry.name == "Name update"
|
|
||||||
|
|
||||||
# Test removal device from device registry using discovery
|
|
||||||
caplog.clear()
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/{service_name}/config", "{}"
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is None
|
|
||||||
assert (
|
|
||||||
"<Event service_removed[L]: domain=notify, service=my_notify_service>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_removed[L]: domain=notify, service=my_notify_service_target1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_removed[L]: domain=notify, service=my_notify_service_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event device_registry_updated[L]: action=remove, device_id={device_id}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_with_device_removal(hass, mqtt_mock, caplog, device_reg):
|
|
||||||
"""Test discovery, update and removal of notify service with a device config."""
|
|
||||||
|
|
||||||
# Initial setup
|
|
||||||
data1 = '{ "command_topic": "test_topic", "name": "My notify service1", "targets": ["target1", "target2"], "device":{"identifiers":["LCD_61236812_ADBA"], "name": "Test123" } }'
|
|
||||||
data2 = '{ "command_topic": "test_topic", "name": "My notify service2", "targets": ["target1", "target2"], "device":{"identifiers":["LCD_61236812_ADBA"], "name": "Test123" } }'
|
|
||||||
service_name1 = "my_notify_service1"
|
|
||||||
service_name2 = "my_notify_service2"
|
|
||||||
await async_setup_notifify_service_with_auto_discovery(
|
|
||||||
hass, mqtt_mock, caplog, device_reg, data1, service_name1
|
|
||||||
)
|
|
||||||
assert "<Event device_registry_updated[L]: action=create, device_id=" in caplog.text
|
|
||||||
await async_setup_notifify_service_with_auto_discovery(
|
|
||||||
hass, mqtt_mock, caplog, device_reg, data2, service_name2
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is not None
|
|
||||||
device_id = device_entry.id
|
|
||||||
assert device_id == device_entry.id
|
|
||||||
assert device_entry.name == "Test123"
|
|
||||||
|
|
||||||
# Remove fist service
|
|
||||||
caplog.clear()
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/{service_name1}/config", "{}"
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name1}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name1}_target1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name1}_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event device_registry_updated[L]: action=remove, device_id={device_id}>"
|
|
||||||
not in caplog.text
|
|
||||||
)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
# The device should still be there
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is not None
|
|
||||||
device_id = device_entry.id
|
|
||||||
assert device_id == device_entry.id
|
|
||||||
assert device_entry.name == "Test123"
|
|
||||||
|
|
||||||
# Test removal device from device registry after removing second service
|
|
||||||
async_fire_mqtt_message(
|
|
||||||
hass, f"homeassistant/{notify.DOMAIN}/{service_name2}/config", "{}"
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is None
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name2}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name2}_target1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name2}_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event device_registry_updated[L]: action=remove, device_id={device_id}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
# Recreate the service and device
|
|
||||||
await async_setup_notifify_service_with_auto_discovery(
|
|
||||||
hass, mqtt_mock, caplog, device_reg, data1, service_name1
|
|
||||||
)
|
|
||||||
assert "<Event device_registry_updated[L]: action=create, device_id=" in caplog.text
|
|
||||||
|
|
||||||
# Test removing the device from the device registry
|
|
||||||
device_entry = device_reg.async_get_device({("mqtt", "LCD_61236812_ADBA")})
|
|
||||||
assert device_entry is not None
|
|
||||||
device_id = device_entry.id
|
|
||||||
caplog.clear()
|
|
||||||
device_reg.async_remove_device(device_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name1}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name1}_target1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event service_removed[L]: domain=notify, service={service_name1}_target2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
f"<Event device_registry_updated[L]: action=remove, device_id={device_id}>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_publishing_with_custom_encoding(hass, mqtt_mock, caplog):
|
|
||||||
"""Test publishing MQTT payload with different encoding via discovery and configuration."""
|
|
||||||
# test with default encoding using configuration setup
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
notify.DOMAIN,
|
|
||||||
{
|
|
||||||
notify.DOMAIN: {
|
|
||||||
"command_topic": "command-topic",
|
|
||||||
"name": "test",
|
|
||||||
"platform": "mqtt",
|
|
||||||
"qos": "2",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
# test with raw encoding and discovery
|
|
||||||
data = '{"name": "test2", "command_topic": "test_topic2", "command_template": "{{ pack(int(message), \'b\') }}" }'
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert "Notify service ('notify', 'bla') has been initialized" in caplog.text
|
|
||||||
assert "<Event service_registered[L]: domain=notify, service=test2>" in caplog.text
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test2",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "4"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with("test_topic2", b"\x04", 0, False)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
# test with utf-16 and update discovery
|
|
||||||
data = '{"encoding":"utf-16", "name": "test3", "command_topic": "test_topic3", "command_template": "{{ message }}" }'
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
"Component has already been discovered: notify bla, sending update"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test3",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"test_topic3", "Message".encode("utf-16"), 0, False
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", "")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert "Notify service ('notify', 'bla') has been removed" in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
async def test_reloadable(hass, mqtt_mock, caplog, tmp_path):
|
|
||||||
"""Test reloading the MQTT platform."""
|
|
||||||
domain = notify.DOMAIN
|
|
||||||
config = DEFAULT_CONFIG[domain]
|
|
||||||
|
|
||||||
# Create and test an old config of 2 entities based on the config supplied
|
|
||||||
old_config_1 = copy.deepcopy(config)
|
|
||||||
old_config_1["name"] = "Test old 1"
|
|
||||||
old_config_2 = copy.deepcopy(config)
|
|
||||||
old_config_2["name"] = "Test old 2"
|
|
||||||
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass, domain, {domain: [old_config_1, old_config_2]}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_old_1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_old_2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
# Add an auto discovered notify target
|
|
||||||
data = '{"name": "Test old 3", "command_topic": "test_topic_discovery" }'
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", data)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert "Notify service ('notify', 'bla') has been initialized" in caplog.text
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_old_3>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create temporary fixture for configuration.yaml based on the supplied config and test a reload with this new config
|
|
||||||
new_config_1 = copy.deepcopy(config)
|
|
||||||
new_config_1["name"] = "Test new 1"
|
|
||||||
new_config_2 = copy.deepcopy(config)
|
|
||||||
new_config_2["name"] = "test new 2"
|
|
||||||
new_config_3 = copy.deepcopy(config)
|
|
||||||
new_config_3["name"] = "test new 3"
|
|
||||||
new_yaml_config_file = tmp_path / "configuration.yaml"
|
|
||||||
new_yaml_config = yaml.dump({domain: [new_config_1, new_config_2, new_config_3]})
|
|
||||||
new_yaml_config_file.write_text(new_yaml_config)
|
|
||||||
assert new_yaml_config_file.read_text() == new_yaml_config
|
|
||||||
|
|
||||||
with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file):
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_RELOAD,
|
|
||||||
{},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"<Event service_removed[L]: domain=notify, service=test_old_1>" in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_removed[L]: domain=notify, service=test_old_2>" in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_new_1>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_new_2>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"<Event service_registered[L]: domain=notify, service=test_new_3>"
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
assert "<Event event_mqtt_reloaded[L]>" in caplog.text
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
# test if the auto discovered item survived the platform reload
|
|
||||||
await hass.services.async_call(
|
|
||||||
notify.DOMAIN,
|
|
||||||
"test_old_3",
|
|
||||||
{notify.ATTR_TITLE: "Title", notify.ATTR_MESSAGE: "Message"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
|
||||||
"test_topic_discovery", "Message", 0, False
|
|
||||||
)
|
|
||||||
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, f"homeassistant/{notify.DOMAIN}/bla/config", "")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert "Notify service ('notify', 'bla') has been removed" in caplog.text
|
|
Loading…
x
Reference in New Issue
Block a user