From 9caaea888cc25d44dfa72c156f865d1d68aed980 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Wed, 17 Oct 2018 15:20:19 +0200 Subject: [PATCH 01/13] Add data/data_template/title to alert component --- homeassistant/components/alert.py | 71 +++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index e224351f9db1f0..afbff3be5b2002 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -5,19 +5,19 @@ https://home-assistant.io/components/alert/ """ import asyncio -from datetime import datetime, timedelta import logging +from datetime import datetime, timedelta import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( - ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY) + ATTR_MESSAGE, ATTR_TITLE, DOMAIN as DOMAIN_NOTIFY) from homeassistant.const import ( CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) -from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers import service, event -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) @@ -29,6 +29,9 @@ CONF_NOTIFIERS = 'notifiers' CONF_REPEAT = 'repeat' CONF_SKIP_FIRST = 'skip_first' +CONF_TITLE = 'title' +CONF_DATA = 'data' +CONF_DATA_TEMPLATE = 'data_template' DEFAULT_CAN_ACK = True DEFAULT_SKIP_FIRST = False @@ -36,6 +39,9 @@ ALERT_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_DONE_MESSAGE): cv.string, + vol.Optional(CONF_TITLE): cv.string, + vol.Optional(CONF_DATA): dict, + vol.Optional(CONF_DATA_TEMPLATE): {cv.match_all: cv.template_complex}, vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_STATE, default=STATE_ON): cv.string, vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]), @@ -49,7 +55,6 @@ }), }, extra=vol.ALLOW_EXTRA) - ALERT_SERVICE_SCHEMA = vol.Schema({ vol.Required(ATTR_ENTITY_ID): cv.entity_ids, }) @@ -82,7 +87,9 @@ async def async_handle_alert_service(service_call): # Setup alerts for entity_id, alert in alerts.items(): entity = Alert(hass, entity_id, - alert[CONF_NAME], alert.get(CONF_DONE_MESSAGE), + alert[CONF_NAME], alert.get(CONF_TITLE), + alert.get(CONF_DATA), alert.get(CONF_DATA_TEMPLATE), + alert.get(CONF_DONE_MESSAGE), alert[CONF_ENTITY_ID], alert[CONF_STATE], alert[CONF_REPEAT], alert[CONF_SKIP_FIRST], alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK]) @@ -109,11 +116,14 @@ async def async_handle_alert_service(service_call): class Alert(ToggleEntity): """Representation of an alert.""" - def __init__(self, hass, entity_id, name, done_message, watched_entity_id, + def __init__(self, hass, entity_id, name, title, data, data_template, done_message, watched_entity_id, state, repeat, skip_first, notifiers, can_ack): """Initialize the alert.""" self.hass = hass self._name = name + self._title = title + self._data = data + self._data_template = data_template self._alert_state = state self._skip_first = skip_first self._notifiers = notifiers @@ -204,18 +214,61 @@ async def _notify(self, *args): if not self._ack: _LOGGER.info("Alerting: %s", self._name) self._send_done_message = True + + msg_payload = {ATTR_MESSAGE: self._name} + + if self._title: + msg_payload.update({ATTR_TITLE: self._title}) + + if self._data: + msg_payload.update(self._data) + elif self._data_template: + def _data_template_creator(value): + """Recursive template creator helper function.""" + if isinstance(value, list): + return [_data_template_creator(item) for item in value] + if isinstance(value, dict): + return {key: _data_template_creator(item) + for key, item in value.items()} + value.hass = self.hass + return value.async_render(self._data_template) + + msg_payload.update(_data_template_creator(self._data_template)) + for target in self._notifiers: await self.hass.services.async_call( - DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name}) + DOMAIN_NOTIFY, target, msg_payload) await self._schedule_notify() async def _notify_done_message(self, *args): """Send notification of complete alert.""" _LOGGER.info("Alerting: %s", self._done_message) self._send_done_message = False + + msg_payload = {ATTR_MESSAGE: self._done_message} + + if self._title: + msg_payload.update({ATTR_TITLE: self._title}) + + if self._data: + msg_payload.update(self._data) + elif self._data_template: + def _data_template_creator(value): + + """Recursive template creator helper function.""" + if isinstance(value, list): + return [_data_template_creator(item) for item in value] + if isinstance(value, dict): + return {key: _data_template_creator(item) + for key, item in value.items()} + value.hass = self.hass + return value.async_render(self._data_template) + + msg_payload.update(_data_template_creator(self._data_template)) + for target in self._notifiers: await self.hass.services.async_call( - DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message}) + DOMAIN_NOTIFY, target, msg_payload) async def async_turn_on(self, **kwargs): """Async Unacknowledge alert.""" From 320a6740620ae7fce5fdfb60b04b0506eb2965d3 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Fri, 19 Oct 2018 15:58:39 +0200 Subject: [PATCH 02/13] Fix line length --- homeassistant/components/alert.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index afbff3be5b2002..170a4aa7541bbe 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -116,8 +116,9 @@ async def async_handle_alert_service(service_call): class Alert(ToggleEntity): """Representation of an alert.""" - def __init__(self, hass, entity_id, name, title, data, data_template, done_message, watched_entity_id, - state, repeat, skip_first, notifiers, can_ack): + def __init__(self, hass, entity_id, name, title, data, data_template, + done_message, watched_entity_id, state, repeat, skip_first, + notifiers, can_ack): """Initialize the alert.""" self.hass = hass self._name = name From f17a0c001745784241f55243e73701ba1e915cb1 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Mon, 22 Oct 2018 09:06:29 +0200 Subject: [PATCH 03/13] Fix tests --- homeassistant/components/alert.py | 20 ++++++++++---------- tests/components/test_alert.py | 21 ++++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index 170a4aa7541bbe..b62a2e44a0cfe4 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -87,12 +87,12 @@ async def async_handle_alert_service(service_call): # Setup alerts for entity_id, alert in alerts.items(): entity = Alert(hass, entity_id, - alert[CONF_NAME], alert.get(CONF_TITLE), - alert.get(CONF_DATA), alert.get(CONF_DATA_TEMPLATE), - alert.get(CONF_DONE_MESSAGE), + alert[CONF_NAME], alert.get(CONF_DONE_MESSAGE), alert[CONF_ENTITY_ID], alert[CONF_STATE], alert[CONF_REPEAT], alert[CONF_SKIP_FIRST], - alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK]) + alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK], + alert.get(CONF_TITLE), alert.get(CONF_DATA), + alert.get(CONF_DATA_TEMPLATE)) all_alerts[entity.entity_id] = entity # Setup service calls @@ -116,15 +116,12 @@ async def async_handle_alert_service(service_call): class Alert(ToggleEntity): """Representation of an alert.""" - def __init__(self, hass, entity_id, name, title, data, data_template, - done_message, watched_entity_id, state, repeat, skip_first, - notifiers, can_ack): + def __init__(self, hass, entity_id, name, done_message, watched_entity_id, + state, repeat, skip_first, notifiers, + can_ack, title, data, data_template): """Initialize the alert.""" self.hass = hass self._name = name - self._title = title - self._data = data - self._data_template = data_template self._alert_state = state self._skip_first = skip_first self._notifiers = notifiers @@ -136,6 +133,9 @@ def __init__(self, hass, entity_id, name, title, data, data_template, self._firing = False self._ack = False + self._title = title + self._data = data + self._data_template = data_template self._cancel = None self._send_done_message = False self.entity_id = ENTITY_ID_FORMAT.format(entity_id) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index 44ece2fc38e01b..d4bfa6239da967 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -1,17 +1,16 @@ """The tests for the Alert component.""" +import unittest # pylint: disable=protected-access from copy import deepcopy -import unittest -from homeassistant.setup import setup_component -from homeassistant.core import callback -from homeassistant.components.alert import DOMAIN import homeassistant.components.alert as alert import homeassistant.components.notify as notify +from homeassistant.components.alert import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON, STATE_OFF) - +from homeassistant.core import callback +from homeassistant.setup import setup_component from tests.common import get_test_home_assistant NAME = "alert_test" @@ -26,10 +25,14 @@ CONF_STATE: STATE_ON, alert.CONF_REPEAT: 30, alert.CONF_SKIP_FIRST: False, - alert.CONF_NOTIFIERS: [NOTIFIER]} - }} + alert.CONF_NOTIFIERS: [NOTIFIER], + alert.CONF_TITLE: '', + alert.CONF_DATA: {}, + alert.CONF_DATA_TEMPLATE: {} + } + }} TEST_NOACK = [NAME, NAME, DONE_MESSAGE, "sensor.test", - STATE_ON, [30], False, NOTIFIER, False] + STATE_ON, [30], False, NOTIFIER, False, None, None, None] ENTITY_ID = alert.ENTITY_ID_FORMAT.format(NAME) @@ -184,7 +187,7 @@ def test_notification_no_done_message(self): """Test notifications.""" events = [] config = deepcopy(TEST_CONFIG) - del(config[alert.DOMAIN][NAME][alert.CONF_DONE_MESSAGE]) + del (config[alert.DOMAIN][NAME][alert.CONF_DONE_MESSAGE]) @callback def record_event(event): From 96d5646d8fe28c5ab2964ee9a967bd643492c919 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Tue, 23 Oct 2018 08:56:51 +0200 Subject: [PATCH 04/13] Fix lint --- homeassistant/components/alert.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index b62a2e44a0cfe4..0d65825934925f 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -255,7 +255,6 @@ async def _notify_done_message(self, *args): msg_payload.update(self._data) elif self._data_template: def _data_template_creator(value): - """Recursive template creator helper function.""" if isinstance(value, list): return [_data_template_creator(item) for item in value] From c563c968d867c7afe22cd983cdf13d235ae2a066 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Thu, 1 Nov 2018 17:18:51 +0100 Subject: [PATCH 05/13] fix line length --- tests/components/test_alert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index b90ee503ccbba0..7f1aa0736b6dae 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -21,7 +21,7 @@ TITLE = 'test' TEST_DATA = { 'data': { - 'inline_keyboard': ['Close garage:/close_garage, Acknowledge:/garage_acknowledge'] + 'inline_keyboard': ['Close garage:/close_garage'] } } TEST_CONFIG = \ From dc3dce21231d49e40cdaca34dc6c432ae58a082d Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Fri, 2 Nov 2018 08:17:35 +0100 Subject: [PATCH 06/13] Fix tests, make title templatable --- homeassistant/components/alert.py | 2 +- tests/components/test_alert.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index 079657511242e4..94989378c7acb8 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -45,7 +45,7 @@ vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean, vol.Optional(CONF_ALERT_MESSAGE): cv.template, vol.Optional(CONF_DONE_MESSAGE): cv.template, - vol.Optional(CONF_TITLE): cv.string, + vol.Optional(CONF_TITLE): cv.template, vol.Optional(CONF_DATA): dict, vol.Required(CONF_NOTIFIERS): cv.ensure_list}) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index 7f1aa0736b6dae..b6ec23118f3382 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -33,9 +33,10 @@ CONF_STATE: STATE_ON, alert.CONF_REPEAT: 30, alert.CONF_SKIP_FIRST: False, - alert.CONF_NOTIFIERS: [NOTIFIER]}, - alert.CONF_TITLE: '', - alert.CONF_DATA: {} + alert.CONF_NOTIFIERS: [NOTIFIER], + alert.CONF_TITLE: TITLE, + alert.CONF_DATA: {} + } }} TEST_NOACK = [NAME, NAME, "sensor.test", STATE_ON, [30], False, None, None, NOTIFIER, False, None, None] From 27254d56a7a732e36d49fb8d5cba0885bb3632bd Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Fri, 2 Nov 2018 09:04:17 +0100 Subject: [PATCH 07/13] Fix test --- tests/components/test_alert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index b6ec23118f3382..0f3cfe1a198a63 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -299,7 +299,7 @@ def test_sending_titled_notification(self): config = deepcopy(TEST_CONFIG) config[alert.DOMAIN][NAME][alert.CONF_TITLE] = TITLE - assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) + assert setup_component(self.hass, alert.DOMAIN, config) self.hass.states.set(TEST_ENTITY, STATE_ON) self.hass.block_till_done() From 45a55a66bb1a05dd3cb6df6c3daf8f8775a417fd Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Fri, 2 Nov 2018 10:24:16 +0100 Subject: [PATCH 08/13] Fix test --- tests/components/test_alert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index 0f3cfe1a198a63..9ba04b68f3bb87 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -313,7 +313,7 @@ def test_sending_data_notification(self): config = deepcopy(TEST_CONFIG) config[alert.DOMAIN][NAME][alert.CONF_DATA] = TEST_DATA - assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) + assert setup_component(self.hass, alert.DOMAIN, config) self.hass.states.set(TEST_ENTITY, STATE_ON) self.hass.block_till_done() From db04e64b07f5767290acc22e219403882d27ab99 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Tue, 6 Nov 2018 09:24:47 +0100 Subject: [PATCH 09/13] Optimize data, make title templated --- homeassistant/components/alert.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index 94989378c7acb8..0ef642451dd48c 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -12,7 +12,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( - ATTR_MESSAGE, ATTR_TITLE, DOMAIN as DOMAIN_NOTIFY) + ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, DOMAIN as DOMAIN_NOTIFY) from homeassistant.const import ( CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) @@ -82,14 +82,14 @@ async def async_setup(hass, config): done_message_template = cfg.get(CONF_DONE_MESSAGE) notifiers = cfg.get(CONF_NOTIFIERS) can_ack = cfg.get(CONF_CAN_ACK) - title = cfg.get(CONF_TITLE) + title_template = cfg.get(CONF_TITLE) data = cfg.get(CONF_DATA) entities.append(Alert(hass, object_id, name, watched_entity_id, alert_state, repeat, skip_first, message_template, done_message_template, notifiers, - can_ack, title, data)) + can_ack, title_template, data)) if not entities: return False @@ -134,14 +134,13 @@ class Alert(ToggleEntity): def __init__(self, hass, entity_id, name, watched_entity_id, state, repeat, skip_first, message_template, - done_message_template, notifiers, can_ack, title, + done_message_template, notifiers, can_ack, title_template, data): """Initialize the alert.""" self.hass = hass self._name = name self._alert_state = state self._skip_first = skip_first - self._title = title self._data = data self._message_template = message_template @@ -152,6 +151,10 @@ def __init__(self, hass, entity_id, name, watched_entity_id, if self._done_message_template is not None: self._done_message_template.hass = hass + self._title_template = title_template + if self._title_template is not None: + self._title_template.hass = hass + self._notifiers = notifiers self._can_ack = can_ack @@ -264,10 +267,12 @@ async def _send_notification_message(self, message): msg_payload = {ATTR_MESSAGE: message} - if self._title: - msg_payload.update({ATTR_TITLE: self._title}) + if self._title_template is not None: + msg_payload.update({ATTR_TITLE: self._title_template.async_render()}) if self._data: - msg_payload.update(self._data) + msg_payload.update({ATTR_DATA: self._data}) + + _LOGGER.debug(msg_payload) for target in self._notifiers: await self.hass.services.async_call( From 9276303fa3eae1e7238371d57993a7ace23f254d Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Tue, 6 Nov 2018 09:26:36 +0100 Subject: [PATCH 10/13] Fix line length --- homeassistant/components/alert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index 0ef642451dd48c..266842ebdbde8a 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -268,7 +268,8 @@ async def _send_notification_message(self, message): msg_payload = {ATTR_MESSAGE: message} if self._title_template is not None: - msg_payload.update({ATTR_TITLE: self._title_template.async_render()}) + title = self._title_template.async_render() + msg_payload.update({ATTR_TITLE: title}) if self._data: msg_payload.update({ATTR_DATA: self._data}) From 6e9ca17224cb70c9db211f07635a1c7acc454c22 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Tue, 22 Jan 2019 14:43:48 +0100 Subject: [PATCH 11/13] Add title template --- tests/components/test_alert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index aa7f5e2823d7ee..c68b13bd164859 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -18,7 +18,7 @@ NOTIFIER = 'test' TEMPLATE = "{{ states.sensor.test.entity_id }}" TEST_ENTITY = "sensor.test" -TITLE = 'test' +tTITLE = "{{ states.sensor.test.entity_id }}" TEST_DATA = { 'data': { 'inline_keyboard': ['Close garage:/close_garage'] From bde607c669493b7fb129c4ebb913f484e097774e Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Tue, 22 Jan 2019 14:44:57 +0100 Subject: [PATCH 12/13] typo --- tests/components/test_alert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index c68b13bd164859..5d052f9942d141 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -18,7 +18,7 @@ NOTIFIER = 'test' TEMPLATE = "{{ states.sensor.test.entity_id }}" TEST_ENTITY = "sensor.test" -tTITLE = "{{ states.sensor.test.entity_id }}" +TITLE = "{{ states.sensor.test.entity_id }}" TEST_DATA = { 'data': { 'inline_keyboard': ['Close garage:/close_garage'] From 212fb516d3104e9ef07d666350443f073f30d7a4 Mon Sep 17 00:00:00 2001 From: Frank Klaassen Date: Wed, 23 Jan 2019 08:07:07 +0100 Subject: [PATCH 13/13] Fix tests --- tests/components/test_alert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index 5d052f9942d141..57da830203e6de 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -19,6 +19,7 @@ TEMPLATE = "{{ states.sensor.test.entity_id }}" TEST_ENTITY = "sensor.test" TITLE = "{{ states.sensor.test.entity_id }}" +TEST_TITLE = "sensor.test" TEST_DATA = { 'data': { 'inline_keyboard': ['Close garage:/close_garage'] @@ -306,7 +307,7 @@ def test_sending_titled_notification(self): self.hass.block_till_done() self.assertEqual(1, len(events)) last_event = events[-1] - self.assertEqual(last_event.data[notify.ATTR_TITLE], TITLE) + self.assertEqual(last_event.data[notify.ATTR_TITLE], TEST_TITLE) def test_sending_data_notification(self): """Test notifications."""