Add message template support for alert component (#17516)
* Add problem text to message if available
* Revert "Add problem text to message if available"
This reverts commit 7be519bf7f
.
* Cleanup setup
* Add message template support
* Fix for failing test
* Added tests
* Refactor changes
* Fix lint violation
* Fix failing tests
* Unify handling for message and done_message parameter and sending function
* Update tests
* Fix lint warnings
pull/18083/head
parent
4163889c6b
commit
bfa86b8138
|
@ -24,23 +24,25 @@ _LOGGER = logging.getLogger(__name__)
|
|||
DOMAIN = 'alert'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
CONF_DONE_MESSAGE = 'done_message'
|
||||
CONF_CAN_ACK = 'can_acknowledge'
|
||||
CONF_NOTIFIERS = 'notifiers'
|
||||
CONF_REPEAT = 'repeat'
|
||||
CONF_SKIP_FIRST = 'skip_first'
|
||||
CONF_ALERT_MESSAGE = 'message'
|
||||
CONF_DONE_MESSAGE = 'done_message'
|
||||
|
||||
DEFAULT_CAN_ACK = True
|
||||
DEFAULT_SKIP_FIRST = False
|
||||
|
||||
ALERT_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_DONE_MESSAGE): cv.string,
|
||||
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)]),
|
||||
vol.Required(CONF_CAN_ACK, default=DEFAULT_CAN_ACK): cv.boolean,
|
||||
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.Required(CONF_NOTIFIERS): cv.ensure_list})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
|
@ -62,31 +64,47 @@ def is_on(hass, entity_id):
|
|||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Alert component."""
|
||||
alerts = config.get(DOMAIN)
|
||||
all_alerts = {}
|
||||
entities = []
|
||||
|
||||
for object_id, cfg in config[DOMAIN].items():
|
||||
if not cfg:
|
||||
cfg = {}
|
||||
|
||||
name = cfg.get(CONF_NAME)
|
||||
watched_entity_id = cfg.get(CONF_ENTITY_ID)
|
||||
alert_state = cfg.get(CONF_STATE)
|
||||
repeat = cfg.get(CONF_REPEAT)
|
||||
skip_first = cfg.get(CONF_SKIP_FIRST)
|
||||
message_template = cfg.get(CONF_ALERT_MESSAGE)
|
||||
done_message_template = cfg.get(CONF_DONE_MESSAGE)
|
||||
notifiers = cfg.get(CONF_NOTIFIERS)
|
||||
can_ack = cfg.get(CONF_CAN_ACK)
|
||||
|
||||
entities.append(Alert(hass, object_id, name,
|
||||
watched_entity_id, alert_state, repeat,
|
||||
skip_first, message_template,
|
||||
done_message_template, notifiers,
|
||||
can_ack))
|
||||
|
||||
if not entities:
|
||||
return False
|
||||
|
||||
async def async_handle_alert_service(service_call):
|
||||
"""Handle calls to alert services."""
|
||||
alert_ids = service.extract_entity_ids(hass, service_call)
|
||||
|
||||
for alert_id in alert_ids:
|
||||
alert = all_alerts[alert_id]
|
||||
alert.async_set_context(service_call.context)
|
||||
if service_call.service == SERVICE_TURN_ON:
|
||||
await alert.async_turn_on()
|
||||
elif service_call.service == SERVICE_TOGGLE:
|
||||
await alert.async_toggle()
|
||||
else:
|
||||
await alert.async_turn_off()
|
||||
for alert in entities:
|
||||
if alert.entity_id != alert_id:
|
||||
continue
|
||||
|
||||
# Setup alerts
|
||||
for entity_id, alert in alerts.items():
|
||||
entity = Alert(hass, entity_id,
|
||||
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])
|
||||
all_alerts[entity.entity_id] = entity
|
||||
alert.async_set_context(service_call.context)
|
||||
if service_call.service == SERVICE_TURN_ON:
|
||||
await alert.async_turn_on()
|
||||
elif service_call.service == SERVICE_TOGGLE:
|
||||
await alert.async_toggle()
|
||||
else:
|
||||
await alert.async_turn_off()
|
||||
|
||||
# Setup service calls
|
||||
hass.services.async_register(
|
||||
|
@ -99,7 +117,7 @@ async def async_setup(hass, config):
|
|||
DOMAIN, SERVICE_TOGGLE, async_handle_alert_service,
|
||||
schema=ALERT_SERVICE_SCHEMA)
|
||||
|
||||
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
|
||||
tasks = [alert.async_update_ha_state() for alert in entities]
|
||||
if tasks:
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
|
@ -109,16 +127,25 @@ async def async_setup(hass, config):
|
|||
class Alert(ToggleEntity):
|
||||
"""Representation of an alert."""
|
||||
|
||||
def __init__(self, hass, entity_id, name, done_message, watched_entity_id,
|
||||
state, repeat, skip_first, notifiers, can_ack):
|
||||
def __init__(self, hass, entity_id, name, watched_entity_id,
|
||||
state, repeat, skip_first, message_template,
|
||||
done_message_template, notifiers, can_ack):
|
||||
"""Initialize the alert."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self._alert_state = state
|
||||
self._skip_first = skip_first
|
||||
|
||||
self._message_template = message_template
|
||||
if self._message_template is not None:
|
||||
self._message_template.hass = hass
|
||||
|
||||
self._done_message_template = done_message_template
|
||||
if self._done_message_template is not None:
|
||||
self._done_message_template.hass = hass
|
||||
|
||||
self._notifiers = notifiers
|
||||
self._can_ack = can_ack
|
||||
self._done_message = done_message
|
||||
|
||||
self._delay = [timedelta(minutes=val) for val in repeat]
|
||||
self._next_delay = 0
|
||||
|
@ -184,7 +211,7 @@ class Alert(ToggleEntity):
|
|||
self._cancel()
|
||||
self._ack = False
|
||||
self._firing = False
|
||||
if self._done_message and self._send_done_message:
|
||||
if self._send_done_message:
|
||||
await self._notify_done_message()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
|
@ -204,18 +231,31 @@ class Alert(ToggleEntity):
|
|||
if not self._ack:
|
||||
_LOGGER.info("Alerting: %s", self._name)
|
||||
self._send_done_message = True
|
||||
for target in self._notifiers:
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name})
|
||||
|
||||
if self._message_template is not None:
|
||||
message = self._message_template.async_render()
|
||||
else:
|
||||
message = self._name
|
||||
|
||||
await self._send_notification_message(message)
|
||||
await self._schedule_notify()
|
||||
|
||||
async def _notify_done_message(self, *args):
|
||||
"""Send notification of complete alert."""
|
||||
_LOGGER.info("Alerting: %s", self._done_message)
|
||||
_LOGGER.info("Alerting: %s", self._done_message_template)
|
||||
self._send_done_message = False
|
||||
|
||||
if self._done_message_template is None:
|
||||
return
|
||||
|
||||
message = self._done_message_template.async_render()
|
||||
|
||||
await self._send_notification_message(message)
|
||||
|
||||
async def _send_notification_message(self, message):
|
||||
for target in self._notifiers:
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message})
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: message})
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Async Unacknowledge alert."""
|
||||
|
|
|
@ -17,19 +17,21 @@ from tests.common import get_test_home_assistant
|
|||
NAME = "alert_test"
|
||||
DONE_MESSAGE = "alert_gone"
|
||||
NOTIFIER = 'test'
|
||||
TEMPLATE = "{{ states.sensor.test.entity_id }}"
|
||||
TEST_ENTITY = "sensor.test"
|
||||
TEST_CONFIG = \
|
||||
{alert.DOMAIN: {
|
||||
NAME: {
|
||||
CONF_NAME: NAME,
|
||||
alert.CONF_DONE_MESSAGE: DONE_MESSAGE,
|
||||
CONF_ENTITY_ID: "sensor.test",
|
||||
CONF_ENTITY_ID: TEST_ENTITY,
|
||||
CONF_STATE: STATE_ON,
|
||||
alert.CONF_REPEAT: 30,
|
||||
alert.CONF_SKIP_FIRST: False,
|
||||
alert.CONF_NOTIFIERS: [NOTIFIER]}
|
||||
}}
|
||||
TEST_NOACK = [NAME, NAME, DONE_MESSAGE, "sensor.test",
|
||||
STATE_ON, [30], False, NOTIFIER, False]
|
||||
TEST_NOACK = [NAME, NAME, "sensor.test",
|
||||
STATE_ON, [30], False, None, None, NOTIFIER, False]
|
||||
ENTITY_ID = alert.ENTITY_ID_FORMAT.format(NAME)
|
||||
|
||||
|
||||
|
@ -102,6 +104,19 @@ class TestAlert(unittest.TestCase):
|
|||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def _setup_notify(self):
|
||||
events = []
|
||||
|
||||
@callback
|
||||
def record_event(event):
|
||||
"""Add recorded event to set."""
|
||||
events.append(event)
|
||||
|
||||
self.hass.services.register(
|
||||
notify.DOMAIN, NOTIFIER, record_event)
|
||||
|
||||
return events
|
||||
|
||||
def test_is_on(self):
|
||||
"""Test is_on method."""
|
||||
self.hass.states.set(ENTITY_ID, STATE_ON)
|
||||
|
@ -228,6 +243,48 @@ class TestAlert(unittest.TestCase):
|
|||
self.hass.block_till_done()
|
||||
assert 2 == len(events)
|
||||
|
||||
def test_sending_non_templated_notification(self):
|
||||
"""Test notifications."""
|
||||
events = self._setup_notify()
|
||||
|
||||
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||
|
||||
self.hass.states.set(TEST_ENTITY, STATE_ON)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(1, len(events))
|
||||
last_event = events[-1]
|
||||
self.assertEqual(last_event.data[notify.ATTR_MESSAGE], NAME)
|
||||
|
||||
def test_sending_templated_notification(self):
|
||||
"""Test templated notification."""
|
||||
events = self._setup_notify()
|
||||
|
||||
config = deepcopy(TEST_CONFIG)
|
||||
config[alert.DOMAIN][NAME][alert.CONF_ALERT_MESSAGE] = TEMPLATE
|
||||
assert setup_component(self.hass, alert.DOMAIN, config)
|
||||
|
||||
self.hass.states.set(TEST_ENTITY, STATE_ON)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(1, len(events))
|
||||
last_event = events[-1]
|
||||
self.assertEqual(last_event.data[notify.ATTR_MESSAGE], TEST_ENTITY)
|
||||
|
||||
def test_sending_templated_done_notification(self):
|
||||
"""Test templated notification."""
|
||||
events = self._setup_notify()
|
||||
|
||||
config = deepcopy(TEST_CONFIG)
|
||||
config[alert.DOMAIN][NAME][alert.CONF_DONE_MESSAGE] = TEMPLATE
|
||||
assert setup_component(self.hass, alert.DOMAIN, config)
|
||||
|
||||
self.hass.states.set(TEST_ENTITY, STATE_ON)
|
||||
self.hass.block_till_done()
|
||||
self.hass.states.set(TEST_ENTITY, STATE_OFF)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(2, len(events))
|
||||
last_event = events[-1]
|
||||
self.assertEqual(last_event.data[notify.ATTR_MESSAGE], TEST_ENTITY)
|
||||
|
||||
def test_skipfirst(self):
|
||||
"""Test skipping first notification."""
|
||||
config = deepcopy(TEST_CONFIG)
|
||||
|
|
Loading…
Reference in New Issue