mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Alert Component (#5201)
This commit is contained in:
parent
dbc2f6b9cd
commit
537355924f
275
homeassistant/components/alert.py
Normal file
275
homeassistant/components/alert.py
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
"""
|
||||||
|
Support for repeating alerts when conditions are met.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/alert/
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.config import load_yaml_config_file
|
||||||
|
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
|
||||||
|
from homeassistant.util.async import run_callback_threadsafe
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = 'alert'
|
||||||
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
|
CONF_CAN_ACK = 'can_acknowledge'
|
||||||
|
CONF_NOTIFIERS = 'notifiers'
|
||||||
|
CONF_REPEAT = 'repeat'
|
||||||
|
CONF_SKIP_FIRST = 'skip_first'
|
||||||
|
|
||||||
|
ALERT_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_NAME): 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=True): cv.boolean,
|
||||||
|
vol.Required(CONF_SKIP_FIRST, default=False): cv.boolean,
|
||||||
|
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
cv.slug: ALERT_SCHEMA,
|
||||||
|
}),
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
ALERT_SERVICE_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def is_on(hass, entity_id):
|
||||||
|
"""Return if the alert is firing and not acknowledged."""
|
||||||
|
return hass.states.is_state(entity_id, STATE_ON)
|
||||||
|
|
||||||
|
|
||||||
|
def turn_on(hass, entity_id):
|
||||||
|
"""Reset the alert."""
|
||||||
|
run_callback_threadsafe(hass.loop, async_turn_on, hass, entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_turn_on(hass, entity_id):
|
||||||
|
"""Async reset the alert."""
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id}
|
||||||
|
hass.async_add_job(
|
||||||
|
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
|
||||||
|
|
||||||
|
|
||||||
|
def turn_off(hass, entity_id):
|
||||||
|
"""Acknowledge alert."""
|
||||||
|
run_callback_threadsafe(hass.loop, async_turn_off, hass, entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_turn_off(hass, entity_id):
|
||||||
|
"""Async acknowledge the alert."""
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id}
|
||||||
|
hass.async_add_job(
|
||||||
|
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
|
||||||
|
|
||||||
|
|
||||||
|
def toggle(hass, entity_id):
|
||||||
|
"""Toggle acknowledgement of alert."""
|
||||||
|
run_callback_threadsafe(hass.loop, async_toggle, hass, entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_toggle(hass, entity_id):
|
||||||
|
"""Async toggle acknowledgement of alert."""
|
||||||
|
data = {ATTR_ENTITY_ID: entity_id}
|
||||||
|
hass.async_add_job(
|
||||||
|
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_setup(hass, config):
|
||||||
|
"""Setup alert component."""
|
||||||
|
alerts = config.get(DOMAIN)
|
||||||
|
all_alerts = {}
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
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]
|
||||||
|
if service_call.service == SERVICE_TURN_ON:
|
||||||
|
yield from alert.async_turn_on()
|
||||||
|
elif service_call.service == SERVICE_TOGGLE:
|
||||||
|
yield from alert.async_toggle()
|
||||||
|
else:
|
||||||
|
yield from alert.async_turn_off()
|
||||||
|
|
||||||
|
# setup alerts
|
||||||
|
for entity_id, alert in alerts.items():
|
||||||
|
entity = Alert(hass, entity_id,
|
||||||
|
alert[CONF_NAME], 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
|
||||||
|
|
||||||
|
# read descriptions
|
||||||
|
descriptions = yield from hass.loop.run_in_executor(
|
||||||
|
None, load_yaml_config_file, os.path.join(
|
||||||
|
os.path.dirname(__file__), 'services.yaml'))
|
||||||
|
descriptions = descriptions.get(DOMAIN, {})
|
||||||
|
|
||||||
|
# setup service calls
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SERVICE_TURN_OFF, async_handle_alert_service,
|
||||||
|
descriptions.get(SERVICE_TURN_OFF), schema=ALERT_SERVICE_SCHEMA)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SERVICE_TURN_ON, async_handle_alert_service,
|
||||||
|
descriptions.get(SERVICE_TURN_ON), schema=ALERT_SERVICE_SCHEMA)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SERVICE_TOGGLE, async_handle_alert_service,
|
||||||
|
descriptions.get(SERVICE_TOGGLE), schema=ALERT_SERVICE_SCHEMA)
|
||||||
|
|
||||||
|
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
|
||||||
|
if tasks:
|
||||||
|
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Alert(ToggleEntity):
|
||||||
|
"""Representation of an alert."""
|
||||||
|
|
||||||
|
def __init__(self, hass, entity_id, name, watched_entity_id, state,
|
||||||
|
repeat, skip_first, notifiers, can_ack):
|
||||||
|
"""Initialize the alert."""
|
||||||
|
self.hass = hass
|
||||||
|
self._name = name
|
||||||
|
self._alert_state = state
|
||||||
|
self._skip_first = skip_first
|
||||||
|
self._notifiers = notifiers
|
||||||
|
self._can_ack = can_ack
|
||||||
|
|
||||||
|
self._delay = [timedelta(minutes=val) for val in repeat]
|
||||||
|
self._next_delay = 0
|
||||||
|
|
||||||
|
self._firing = False
|
||||||
|
self._ack = False
|
||||||
|
self._cancel = None
|
||||||
|
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
|
||||||
|
|
||||||
|
event.async_track_state_change(hass, watched_entity_id,
|
||||||
|
self.watched_entity_change)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the alert."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""HASS need not poll these entities."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the alert status."""
|
||||||
|
if self._firing:
|
||||||
|
if self._ack:
|
||||||
|
return STATE_OFF
|
||||||
|
return STATE_ON
|
||||||
|
return STATE_IDLE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hidden(self):
|
||||||
|
"""Hide the alert when it is not firing."""
|
||||||
|
return not self._can_ack or not self._firing
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def watched_entity_change(self, entity, from_state, to_state):
|
||||||
|
"""Determine if the alert should start or stop."""
|
||||||
|
_LOGGER.debug('Watched entity (%s) has changed.', entity)
|
||||||
|
if to_state.state == self._alert_state and not self._firing:
|
||||||
|
yield from self.begin_alerting()
|
||||||
|
if to_state.state != self._alert_state and self._firing:
|
||||||
|
yield from self.end_alerting()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def begin_alerting(self):
|
||||||
|
"""Begin the alert procedures."""
|
||||||
|
_LOGGER.debug('Beginning Alert: %s', self._name)
|
||||||
|
self._ack = False
|
||||||
|
self._firing = True
|
||||||
|
self._next_delay = 0
|
||||||
|
|
||||||
|
if not self._skip_first:
|
||||||
|
yield from self._notify()
|
||||||
|
else:
|
||||||
|
yield from self._schedule_notify()
|
||||||
|
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def end_alerting(self):
|
||||||
|
"""End the alert procedures."""
|
||||||
|
_LOGGER.debug('Ending Alert: %s', self._name)
|
||||||
|
self._cancel()
|
||||||
|
self._ack = False
|
||||||
|
self._firing = False
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _schedule_notify(self):
|
||||||
|
"""Schedule a notification."""
|
||||||
|
delay = self._delay[self._next_delay]
|
||||||
|
next_msg = datetime.now() + delay
|
||||||
|
self._cancel = \
|
||||||
|
event.async_track_point_in_time(self.hass, self._notify, next_msg)
|
||||||
|
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _notify(self, *args):
|
||||||
|
"""Send the alert notification."""
|
||||||
|
if not self._firing:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._ack:
|
||||||
|
_LOGGER.info('Alerting: %s', self._name)
|
||||||
|
for target in self._notifiers:
|
||||||
|
yield from self.hass.services.async_call(
|
||||||
|
'notify', target, {'message': self._name})
|
||||||
|
yield from self._schedule_notify()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_turn_on(self):
|
||||||
|
"""Async Unacknowledge alert."""
|
||||||
|
_LOGGER.debug('Reset Alert: %s', self._name)
|
||||||
|
self._ack = False
|
||||||
|
yield from self.async_update_ha_state()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_turn_off(self):
|
||||||
|
"""Async Acknowledge alert."""
|
||||||
|
_LOGGER.debug('Acknowledged Alert: %s', self._name)
|
||||||
|
self._ack = True
|
||||||
|
yield from self.async_update_ha_state()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_toggle(self):
|
||||||
|
"""Async toggle alert."""
|
||||||
|
if self._ack:
|
||||||
|
return self.async_turn_on()
|
||||||
|
return self.async_turn_off()
|
@ -211,6 +211,31 @@ verisure:
|
|||||||
description: The serial number of the smartcam you want to capture an image from.
|
description: The serial number of the smartcam you want to capture an image from.
|
||||||
example: '2DEU AT5Z'
|
example: '2DEU AT5Z'
|
||||||
|
|
||||||
|
alert:
|
||||||
|
turn_off:
|
||||||
|
description: Silence alert's notifications.
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the alert to silence.
|
||||||
|
example: 'alert.garage_door_open'
|
||||||
|
|
||||||
|
turn_on:
|
||||||
|
description: Reset alert's notifications.
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the alert to reset.
|
||||||
|
example: 'alert.garage_door_open'
|
||||||
|
|
||||||
|
toggle:
|
||||||
|
description: Toggle alert's notifications.
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the alert to toggle.
|
||||||
|
example: 'alert.garage_door_open'
|
||||||
|
|
||||||
hdmi_cec:
|
hdmi_cec:
|
||||||
send_command:
|
send_command:
|
||||||
description: Sends CEC command into HDMI CEC capable adapter.
|
description: Sends CEC command into HDMI CEC capable adapter.
|
||||||
|
172
tests/components/test_alert.py
Normal file
172
tests/components/test_alert.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
"""The tests for the Alert component."""
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
from copy import deepcopy
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from homeassistant.bootstrap import setup_component
|
||||||
|
from homeassistant.core import callback
|
||||||
|
import homeassistant.components.alert as alert
|
||||||
|
import homeassistant.components.notify as notify
|
||||||
|
from homeassistant.const import (CONF_ENTITY_ID, STATE_IDLE, CONF_NAME,
|
||||||
|
CONF_STATE, STATE_ON, STATE_OFF)
|
||||||
|
|
||||||
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
|
NAME = "alert_test"
|
||||||
|
NOTIFIER = 'test'
|
||||||
|
TEST_CONFIG = \
|
||||||
|
{alert.DOMAIN: {
|
||||||
|
NAME: {
|
||||||
|
CONF_NAME: NAME,
|
||||||
|
CONF_ENTITY_ID: "sensor.test",
|
||||||
|
CONF_STATE: STATE_ON,
|
||||||
|
alert.CONF_REPEAT: 30,
|
||||||
|
alert.CONF_SKIP_FIRST: False,
|
||||||
|
alert.CONF_NOTIFIERS: [NOTIFIER]}
|
||||||
|
}}
|
||||||
|
TEST_NOACK = [NAME, NAME, "sensor.test", STATE_ON,
|
||||||
|
[30], False, NOTIFIER, False]
|
||||||
|
ENTITY_ID = alert.ENTITY_ID_FORMAT.format(NAME)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
class TestAlert(unittest.TestCase):
|
||||||
|
"""Test the alert module."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Stop everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_is_on(self):
|
||||||
|
"""Test is_on method."""
|
||||||
|
self.hass.states.set(ENTITY_ID, STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertTrue(alert.is_on(self.hass, ENTITY_ID))
|
||||||
|
self.hass.states.set(ENTITY_ID, STATE_OFF)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertFalse(alert.is_on(self.hass, ENTITY_ID))
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
"""Test setup method."""
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
self.assertEqual(STATE_IDLE, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
|
||||||
|
def test_fire(self):
|
||||||
|
"""Test the alert firing."""
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
|
||||||
|
def test_silence(self):
|
||||||
|
"""Test silencing the alert."""
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
alert.turn_off(self.hass, ENTITY_ID)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
|
||||||
|
# alert should not be silenced on next fire
|
||||||
|
self.hass.states.set("sensor.test", STATE_OFF)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_IDLE, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
|
||||||
|
def test_reset(self):
|
||||||
|
"""Test resetting the alert."""
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
alert.turn_off(self.hass, ENTITY_ID)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
alert.turn_on(self.hass, ENTITY_ID)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
|
||||||
|
def test_toggle(self):
|
||||||
|
"""Test toggling alert."""
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
alert.toggle(self.hass, ENTITY_ID)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
alert.toggle(self.hass, ENTITY_ID)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
|
||||||
|
|
||||||
|
def test_hidden(self):
|
||||||
|
"""Test entity hidding."""
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
|
||||||
|
self.assertTrue(hidden)
|
||||||
|
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
|
||||||
|
self.assertFalse(hidden)
|
||||||
|
|
||||||
|
alert.turn_off(self.hass, ENTITY_ID)
|
||||||
|
hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
|
||||||
|
self.assertFalse(hidden)
|
||||||
|
|
||||||
|
def test_notification(self):
|
||||||
|
"""Test notifications."""
|
||||||
|
events = []
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def record_event(event):
|
||||||
|
"""Add recorded event to set."""
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
self.hass.services.register(
|
||||||
|
notify.DOMAIN, NOTIFIER, record_event)
|
||||||
|
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
|
||||||
|
self.assertEqual(0, len(events))
|
||||||
|
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(1, len(events))
|
||||||
|
|
||||||
|
self.hass.states.set("sensor.test", STATE_OFF)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(1, len(events))
|
||||||
|
|
||||||
|
def test_skipfirst(self):
|
||||||
|
"""Test skipping first notification."""
|
||||||
|
config = deepcopy(TEST_CONFIG)
|
||||||
|
config[alert.DOMAIN][NAME][alert.CONF_SKIP_FIRST] = True
|
||||||
|
events = []
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def record_event(event):
|
||||||
|
"""Add recorded event to set."""
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
self.hass.services.register(
|
||||||
|
notify.DOMAIN, NOTIFIER, record_event)
|
||||||
|
|
||||||
|
assert setup_component(self.hass, alert.DOMAIN, config)
|
||||||
|
self.assertEqual(0, len(events))
|
||||||
|
|
||||||
|
self.hass.states.set("sensor.test", STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(0, len(events))
|
||||||
|
|
||||||
|
def test_noack(self):
|
||||||
|
"""Test no ack feature."""
|
||||||
|
entity = alert.Alert(self.hass, *TEST_NOACK)
|
||||||
|
self.hass.async_add_job(entity.begin_alerting)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(True, entity.hidden)
|
Loading…
x
Reference in New Issue
Block a user