core/homeassistant/components/notify/__init__.py

292 lines
9.3 KiB
Python
Raw Normal View History

"""Provides functionality to notify people."""
import asyncio
from functools import partial
import logging
from typing import Any, Dict, Optional
import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_PLATFORM
from homeassistant.core import ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform, discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.util import slugify
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
# Platform specific data
2019-07-31 19:25:30 +00:00
ATTR_DATA = "data"
# Text to notify user of
2019-07-31 19:25:30 +00:00
ATTR_MESSAGE = "message"
# Target of the notification (user, device, etc)
2019-07-31 19:25:30 +00:00
ATTR_TARGET = "target"
# Title of notification
2019-07-31 19:25:30 +00:00
ATTR_TITLE = "title"
ATTR_TITLE_DEFAULT = "Home Assistant"
2019-07-31 19:25:30 +00:00
DOMAIN = "notify"
2019-07-31 19:25:30 +00:00
SERVICE_NOTIFY = "notify"
NOTIFY_SERVICES = "notify_services"
2019-07-31 19:25:30 +00:00
PLATFORM_SCHEMA = vol.Schema(
{vol.Required(CONF_PLATFORM): cv.string, vol.Optional(CONF_NAME): cv.string},
extra=vol.ALLOW_EXTRA,
)
2019-07-31 19:25:30 +00:00
NOTIFY_SERVICE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_DATA): dict,
}
)
@bind_hass
async def async_reload(hass: HomeAssistantType, integration_name: str) -> None:
"""Register notify services for an integration."""
if not _async_integration_has_notify_services(hass, integration_name):
return
tasks = [
notify_service.async_register_services()
for notify_service in hass.data[NOTIFY_SERVICES][integration_name]
]
await asyncio.gather(*tasks)
@bind_hass
async def async_reset_platform(hass: HomeAssistantType, integration_name: str) -> None:
"""Unregister notify services for an integration."""
if not _async_integration_has_notify_services(hass, integration_name):
return
tasks = [
notify_service.async_unregister_services()
for notify_service in hass.data[NOTIFY_SERVICES][integration_name]
]
await asyncio.gather(*tasks)
del hass.data[NOTIFY_SERVICES][integration_name]
def _async_integration_has_notify_services(
hass: HomeAssistantType, integration_name: str
) -> bool:
"""Determine if an integration has notify services registered."""
if (
NOTIFY_SERVICES not in hass.data
or integration_name not in hass.data[NOTIFY_SERVICES]
):
return False
return True
class BaseNotificationService:
"""An abstract class for notification services."""
hass: Optional[HomeAssistantType] = None
def send_message(self, message, **kwargs):
"""Send a message.
kwargs can contain ATTR_TITLE to specify a title.
"""
raise NotImplementedError()
async def async_send_message(self, message: Any, **kwargs: Any) -> None:
"""Send a message.
kwargs can contain ATTR_TITLE to specify a title.
"""
await self.hass.async_add_job(partial(self.send_message, message, **kwargs)) # type: ignore
async def _async_notify_message_service(self, service: ServiceCall) -> None:
"""Handle sending notification message service calls."""
kwargs = {}
message = service.data[ATTR_MESSAGE]
title = service.data.get(ATTR_TITLE)
if title:
title.hass = self.hass
kwargs[ATTR_TITLE] = title.async_render()
if self._registered_targets.get(service.service) is not None:
kwargs[ATTR_TARGET] = [self._registered_targets[service.service]]
elif service.data.get(ATTR_TARGET) is not None:
kwargs[ATTR_TARGET] = service.data.get(ATTR_TARGET)
message.hass = self.hass
kwargs[ATTR_MESSAGE] = message.async_render()
kwargs[ATTR_DATA] = service.data.get(ATTR_DATA)
await self.async_send_message(**kwargs)
async def async_setup(
self,
hass: HomeAssistantType,
service_name: str,
target_service_name_prefix: str,
) -> None:
"""Store the data for the notify service."""
# pylint: disable=attribute-defined-outside-init
self.hass = hass
self._service_name = service_name
self._target_service_name_prefix = target_service_name_prefix
self._registered_targets: Dict = {}
async def async_register_services(self) -> None:
"""Create or update the notify services."""
assert self.hass
if hasattr(self, "targets"):
stale_targets = set(self._registered_targets)
# pylint: disable=no-member
for name, target in self.targets.items(): # type: ignore
target_name = slugify(f"{self._target_service_name_prefix}_{name}")
if target_name in stale_targets:
stale_targets.remove(target_name)
if target_name in self._registered_targets:
continue
self._registered_targets[target_name] = target
self.hass.services.async_register(
DOMAIN,
target_name,
self._async_notify_message_service,
schema=NOTIFY_SERVICE_SCHEMA,
)
for stale_target_name in stale_targets:
del self._registered_targets[stale_target_name]
self.hass.services.async_remove(
DOMAIN,
stale_target_name,
)
if self.hass.services.has_service(DOMAIN, self._service_name):
return
self.hass.services.async_register(
DOMAIN,
self._service_name,
self._async_notify_message_service,
schema=NOTIFY_SERVICE_SCHEMA,
)
async def async_unregister_services(self) -> None:
"""Unregister the notify services."""
assert self.hass
if self._registered_targets:
remove_targets = set(self._registered_targets)
for remove_target_name in remove_targets:
del self._registered_targets[remove_target_name]
self.hass.services.async_remove(
DOMAIN,
remove_target_name,
)
if not self.hass.services.has_service(DOMAIN, self._service_name):
return
self.hass.services.async_remove(
DOMAIN,
self._service_name,
)
async def async_setup(hass, config):
"""Set up the notify services."""
hass.data.setdefault(NOTIFY_SERVICES, {})
async def async_setup_platform(
integration_name, p_config=None, discovery_info=None
):
"""Set up a notify platform."""
if p_config is None:
p_config = {}
platform = await async_prepare_setup_platform(
hass, config, DOMAIN, integration_name
)
if platform is None:
_LOGGER.error("Unknown notification service specified")
return
_LOGGER.info("Setting up %s.%s", DOMAIN, integration_name)
notify_service = None
try:
2019-07-31 19:25:30 +00:00
if hasattr(platform, "async_get_service"):
notify_service = await platform.async_get_service(
hass, p_config, discovery_info
)
elif hasattr(platform, "get_service"):
notify_service = await hass.async_add_job(
2019-07-31 19:25:30 +00:00
platform.get_service, hass, p_config, discovery_info
)
else:
raise HomeAssistantError("Invalid notify platform.")
if notify_service is None:
Stable and asynchronous KNX library. (#8725) * First draft of XKNX module for Home-Assistant * XKNX does now take path of xknx.yaml as parameter * small fix, telegram_received_callback has different signature * changed method of registering callbacks of devices * removed non async command lines from xknx * telegram_received_cb not needed within HASS module * updated requirements * Configuration if XKNX should connect via Routing or Tunneling * bumping version to 0.6.1 * small fix within xknx plugin * bumped version * XKNX-Switches are now BinarySensors and Logic from Sensor was moved to BinarySensor * renamed Outlet to Switch * pylint * configuration of KNX lights via HASS config, yay! * changed name of attribute * Added configuration for xknx to switch component * added support for sensors within hass configuration * added support for climate within hass configuration * Thermostat -> Climate * added configuration support for binary_sensors * renamed Shutter to Cover * added configuration support for cover * restructured file structure according to HASS requirements * pylint * pylint * pylint * pylint * pylint * pylint * updated version * pylint * pylint * pylint * added setpoint support for climate devices * devices are now in a different module * more asyncio :-) * pydocstyle * pydocstyle * added actions to binary_sensor * allow more than one automation * readded requirement * Modifications suggested by hound * Modifications suggested by hound * Modifications suggested by hound * Modifications suggested by hound * xknx now imported as local import * hound *sigh* * lint * 'fixed' coverage. * next try for getting gen_requirements_all.py working * removed blank line * XKNX 0.7.1 with logging functionality, replaced some print() calls with _LOGGER * updated requirements_all.txt * Fixes issue https://github.com/XKNX/xknx/issues/51 * https://github.com/XKNX/xknx/issues/52 added raw access to KNX bus from HASS component. * bumped version - 0.7.3 contains some bugfixes * bumped version - 0.7.3 contains some bugfixes * setting setpoint within climate device has to be async * bumped version to 0.7.4 * bumped version * https://github.com/XKNX/xknx/issues/48 Adding HVAC support. * pylint suggestions * Made target temperature and set point required attributes * renamed value_type to type within sensor configuration * Issue https://github.com/XKNX/xknx/issues/52 : added filter functionality for not flooding the event bus. * suggestions by pylint * Added notify support for knx platform. * logging error if discovery_info is None. * review suggestions by @armills * line too long * Using discovery_info to notifiy component which devices should be added. * moved XKNX automation to main level. * renamed xknx component to knx. * reverted change within .coveragerc * changed dependency * updated docstrings. * updated version of xknx within requirements_all.txt * moved requirement to correct position * renamed configuration attribute * added @callback-decorator and async_prefix. * added @callback decorator and async_ prefix to register_callbacks functions * fixed typo * pylint suggestions * added angle position and invert_position and invert_angle to cover.knx * typo * bumped version within requirements_all.txt * bumped version * Added support for HVAC controller status
2017-09-07 07:11:55 +00:00
# Platforms can decide not to create a service based
# on discovery data.
if discovery_info is None:
_LOGGER.error(
"Failed to initialize notification service %s", integration_name
2019-07-31 19:25:30 +00:00
)
return
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error setting up platform %s", integration_name)
return
Stable and asynchronous KNX library. (#8725) * First draft of XKNX module for Home-Assistant * XKNX does now take path of xknx.yaml as parameter * small fix, telegram_received_callback has different signature * changed method of registering callbacks of devices * removed non async command lines from xknx * telegram_received_cb not needed within HASS module * updated requirements * Configuration if XKNX should connect via Routing or Tunneling * bumping version to 0.6.1 * small fix within xknx plugin * bumped version * XKNX-Switches are now BinarySensors and Logic from Sensor was moved to BinarySensor * renamed Outlet to Switch * pylint * configuration of KNX lights via HASS config, yay! * changed name of attribute * Added configuration for xknx to switch component * added support for sensors within hass configuration * added support for climate within hass configuration * Thermostat -> Climate * added configuration support for binary_sensors * renamed Shutter to Cover * added configuration support for cover * restructured file structure according to HASS requirements * pylint * pylint * pylint * pylint * pylint * pylint * updated version * pylint * pylint * pylint * added setpoint support for climate devices * devices are now in a different module * more asyncio :-) * pydocstyle * pydocstyle * added actions to binary_sensor * allow more than one automation * readded requirement * Modifications suggested by hound * Modifications suggested by hound * Modifications suggested by hound * Modifications suggested by hound * xknx now imported as local import * hound *sigh* * lint * 'fixed' coverage. * next try for getting gen_requirements_all.py working * removed blank line * XKNX 0.7.1 with logging functionality, replaced some print() calls with _LOGGER * updated requirements_all.txt * Fixes issue https://github.com/XKNX/xknx/issues/51 * https://github.com/XKNX/xknx/issues/52 added raw access to KNX bus from HASS component. * bumped version - 0.7.3 contains some bugfixes * bumped version - 0.7.3 contains some bugfixes * setting setpoint within climate device has to be async * bumped version to 0.7.4 * bumped version * https://github.com/XKNX/xknx/issues/48 Adding HVAC support. * pylint suggestions * Made target temperature and set point required attributes * renamed value_type to type within sensor configuration * Issue https://github.com/XKNX/xknx/issues/52 : added filter functionality for not flooding the event bus. * suggestions by pylint * Added notify support for knx platform. * logging error if discovery_info is None. * review suggestions by @armills * line too long * Using discovery_info to notifiy component which devices should be added. * moved XKNX automation to main level. * renamed xknx component to knx. * reverted change within .coveragerc * changed dependency * updated docstrings. * updated version of xknx within requirements_all.txt * moved requirement to correct position * renamed configuration attribute * added @callback-decorator and async_prefix. * added @callback decorator and async_ prefix to register_callbacks functions * fixed typo * pylint suggestions * added angle position and invert_position and invert_angle to cover.knx * typo * bumped version within requirements_all.txt * bumped version * Added support for HVAC controller status
2017-09-07 07:11:55 +00:00
if discovery_info is None:
discovery_info = {}
conf_name = p_config.get(CONF_NAME) or discovery_info.get(CONF_NAME)
target_service_name_prefix = conf_name or integration_name
service_name = slugify(conf_name or SERVICE_NOTIFY)
await notify_service.async_setup(hass, service_name, target_service_name_prefix)
await notify_service.async_register_services()
hass.data[NOTIFY_SERVICES].setdefault(integration_name, []).append(
notify_service
)
hass.config.components.add(f"{DOMAIN}.{integration_name}")
return True
2019-07-31 19:25:30 +00:00
setup_tasks = [
async_setup_platform(integration_name, p_config)
for integration_name, p_config in config_per_platform(config, DOMAIN)
2019-07-31 19:25:30 +00:00
]
if setup_tasks:
await asyncio.wait(setup_tasks)
async def async_platform_discovered(platform, info):
"""Handle for discovered platform."""
await async_setup_platform(platform, discovery_info=info)
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
return True