From 206e7d7a678fe4ecd1dd18497992af251cf1d78d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 25 Jun 2016 16:40:33 -0700 Subject: [PATCH] Extend persistent notification support (#2371) --- homeassistant/bootstrap.py | 5 +- homeassistant/components/demo.py | 6 ++ .../components/persistent_notification.py | 70 +++++++++++++++++-- .../test_persistent_notification.py | 65 +++++++++++++++++ 4 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 tests/components/test_persistent_notification.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 754d4f4f5aa..ff7e73a00f1 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -11,7 +11,7 @@ from threading import RLock import voluptuous as vol import homeassistant.components as core_components -import homeassistant.components.group as group +from homeassistant.components import group, persistent_notification import homeassistant.config as config_util import homeassistant.core as core import homeassistant.helpers.config_validation as cv @@ -262,9 +262,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True, if not core_components.setup(hass, config): _LOGGER.error('Home Assistant core failed to initialize. ' 'Further initialization aborted.') - return hass + persistent_notification.setup(hass, config) + _LOGGER.info('Home Assistant core initialized') # Give event decorators access to HASS diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 148c57a12c3..f083a96f5b2 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -37,6 +37,7 @@ def setup(hass, config): """Setup a demo environment.""" group = loader.get_component('group') configurator = loader.get_component('configurator') + persistent_notification = loader.get_component('persistent_notification') config.setdefault(ha.DOMAIN, {}) config.setdefault(DOMAIN, {}) @@ -59,6 +60,11 @@ def setup(hass, config): demo_config[component] = {CONF_PLATFORM: 'demo'} bootstrap.setup_component(hass, component, demo_config) + # Setup example persistent notification + persistent_notification.create( + hass, 'This is an example of a persistent notification.', + title='Example Notification') + # Setup room groups lights = sorted(hass.states.entity_ids('light')) switches = sorted(hass.states.entity_ids('switch')) diff --git a/homeassistant/components/persistent_notification.py b/homeassistant/components/persistent_notification.py index 6c784eaf5ca..66a634616fa 100644 --- a/homeassistant/components/persistent_notification.py +++ b/homeassistant/components/persistent_notification.py @@ -4,15 +4,77 @@ A component which is collecting configuration errors. For more details about this component, please refer to the documentation at https://home-assistant.io/components/persistent_notification/ """ +import logging -DOMAIN = "persistent_notification" +import voluptuous as vol + +from homeassistant.exceptions import TemplateError +from homeassistant.helpers import template, config_validation as cv +from homeassistant.helpers.entity import generate_entity_id +from homeassistant.util import slugify + +DOMAIN = 'persistent_notification' +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +SERVICE_CREATE = 'create' +ATTR_TITLE = 'title' +ATTR_MESSAGE = 'message' +ATTR_NOTIFICATION_ID = 'notification_id' + +SCHEMA_SERVICE_CREATE = vol.Schema({ + vol.Required(ATTR_MESSAGE): cv.template, + vol.Optional(ATTR_TITLE): cv.template, + vol.Optional(ATTR_NOTIFICATION_ID): cv.string, +}) -def create(hass, entity, msg): - """Create a state for an error.""" - hass.states.set('{}.{}'.format(DOMAIN, entity), msg) +DEFAULT_OBJECT_ID = 'notification' +_LOGGER = logging.getLogger(__name__) + + +def create(hass, message, title=None, notification_id=None): + """Turn all or specified light off.""" + data = { + key: value for key, value in [ + (ATTR_TITLE, title), + (ATTR_MESSAGE, message), + (ATTR_NOTIFICATION_ID, notification_id), + ] if value is not None + } + + hass.services.call(DOMAIN, SERVICE_CREATE, data) def setup(hass, config): """Setup the persistent notification component.""" + def create_service(call): + """Handle a create notification service call.""" + title = call.data.get(ATTR_TITLE) + message = call.data.get(ATTR_MESSAGE) + notification_id = call.data.get(ATTR_NOTIFICATION_ID) + + if notification_id is not None: + entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id)) + else: + entity_id = generate_entity_id(ENTITY_ID_FORMAT, DEFAULT_OBJECT_ID, + hass=hass) + attr = {} + if title is not None: + try: + title = template.render(hass, title) + except TemplateError as ex: + _LOGGER.error('Error rendering title %s: %s', title, ex) + + attr[ATTR_TITLE] = title + + try: + message = template.render(hass, message) + except TemplateError as ex: + _LOGGER.error('Error rendering message %s: %s', message, ex) + + hass.states.set(entity_id, message, attr) + + hass.services.register(DOMAIN, SERVICE_CREATE, create_service, {}, + SCHEMA_SERVICE_CREATE) + return True diff --git a/tests/components/test_persistent_notification.py b/tests/components/test_persistent_notification.py new file mode 100644 index 00000000000..6f6d8b8e1b0 --- /dev/null +++ b/tests/components/test_persistent_notification.py @@ -0,0 +1,65 @@ +"""The tests for the persistent notification component.""" +import homeassistant.components.persistent_notification as pn + +from tests.common import get_test_home_assistant + + +class TestPersistentNotification: + """Test persistent notification component.""" + + def setup_method(self, method): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + pn.setup(self.hass, {}) + + def teardown_method(self, method): + """Stop everything that was started.""" + self.hass.stop() + + def test_create(self): + """Test creating notification without title or notification id.""" + assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 + + pn.create(self.hass, 'Hello World {{ 1 + 1 }}', + title='{{ 1 + 1 }} beers') + self.hass.pool.block_till_done() + + entity_ids = self.hass.states.entity_ids(pn.DOMAIN) + assert len(entity_ids) == 1 + + state = self.hass.states.get(entity_ids[0]) + assert state.state == 'Hello World 2' + assert state.attributes.get('title') == '2 beers' + + def test_create_notification_id(self): + """Ensure overwrites existing notification with same id.""" + assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 + + pn.create(self.hass, 'test', notification_id='Beer 2') + self.hass.pool.block_till_done() + + assert len(self.hass.states.entity_ids()) == 1 + state = self.hass.states.get('persistent_notification.beer_2') + assert state.state == 'test' + + pn.create(self.hass, 'test 2', notification_id='Beer 2') + self.hass.pool.block_till_done() + + # We should have overwritten old one + assert len(self.hass.states.entity_ids()) == 1 + state = self.hass.states.get('persistent_notification.beer_2') + assert state.state == 'test 2' + + def test_create_template_error(self): + """Ensure we output templates if contain error.""" + assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 + + pn.create(self.hass, '{{ message + 1 }}', '{{ title + 1 }}') + self.hass.pool.block_till_done() + + entity_ids = self.hass.states.entity_ids(pn.DOMAIN) + assert len(entity_ids) == 1 + + state = self.hass.states.get(entity_ids[0]) + assert state.state == '{{ message + 1 }}' + assert state.attributes.get('title') == '{{ title + 1 }}'