Add service to alarm control panel for night mode arming (#8614)
* Update const.py * Update __init__.py * Update services.yaml * Update totalconnect.py * Update manual.py Add night arm service for manual alarm control panel * Update test_manual.py Add tests for night mode arming * Update manual.py Fix docstring
This commit is contained in:
parent
c92e5c147a
commit
811fdc5533
6 changed files with 152 additions and 8 deletions
|
@ -13,7 +13,8 @@ import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
||||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
|
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
|
||||||
|
SERVICE_ALARM_ARM_NIGHT)
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||||
|
@ -31,6 +32,7 @@ SERVICE_TO_METHOD = {
|
||||||
SERVICE_ALARM_DISARM: 'alarm_disarm',
|
SERVICE_ALARM_DISARM: 'alarm_disarm',
|
||||||
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
|
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
|
||||||
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
|
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
|
||||||
|
SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night',
|
||||||
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
|
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +83,18 @@ def alarm_arm_away(hass, code=None, entity_id=None):
|
||||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
def alarm_arm_night(hass, code=None, entity_id=None):
|
||||||
|
"""Send the alarm the command for arm night."""
|
||||||
|
data = {}
|
||||||
|
if code:
|
||||||
|
data[ATTR_CODE] = code
|
||||||
|
if entity_id:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def alarm_trigger(hass, code=None, entity_id=None):
|
def alarm_trigger(hass, code=None, entity_id=None):
|
||||||
"""Send the alarm the command for trigger."""
|
"""Send the alarm the command for trigger."""
|
||||||
|
@ -187,6 +201,17 @@ class AlarmControlPanel(Entity):
|
||||||
"""
|
"""
|
||||||
return self.hass.async_add_job(self.alarm_arm_away, code)
|
return self.hass.async_add_job(self.alarm_arm_away, code)
|
||||||
|
|
||||||
|
def alarm_arm_night(self, code=None):
|
||||||
|
"""Send arm night command."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def async_alarm_arm_night(self, code=None):
|
||||||
|
"""Send arm night command.
|
||||||
|
|
||||||
|
This method must be run in the event loop and returns a coroutine.
|
||||||
|
"""
|
||||||
|
return self.hass.async_add_job(self.alarm_arm_night, code)
|
||||||
|
|
||||||
def alarm_trigger(self, code=None):
|
def alarm_trigger(self, code=None):
|
||||||
"""Send alarm trigger command."""
|
"""Send alarm trigger command."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
@ -12,9 +12,10 @@ import voluptuous as vol
|
||||||
import homeassistant.components.alarm_control_panel as alarm
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
|
||||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME,
|
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
|
||||||
CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
|
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
|
||||||
|
CONF_DISARM_AFTER_TRIGGER)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.event import track_point_in_time
|
from homeassistant.helpers.event import track_point_in_time
|
||||||
|
|
||||||
|
@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
if self._state in (STATE_ALARM_ARMED_HOME,
|
if self._state in (STATE_ALARM_ARMED_HOME,
|
||||||
STATE_ALARM_ARMED_AWAY) and \
|
STATE_ALARM_ARMED_AWAY,
|
||||||
|
STATE_ALARM_ARMED_NIGHT) and \
|
||||||
self._pending_time and self._state_ts + self._pending_time > \
|
self._pending_time and self._state_ts + self._pending_time > \
|
||||||
dt_util.utcnow():
|
dt_util.utcnow():
|
||||||
return STATE_ALARM_PENDING
|
return STATE_ALARM_PENDING
|
||||||
|
@ -145,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||||
self._hass, self.async_update_ha_state,
|
self._hass, self.async_update_ha_state,
|
||||||
self._state_ts + self._pending_time)
|
self._state_ts + self._pending_time)
|
||||||
|
|
||||||
|
def alarm_arm_night(self, code=None):
|
||||||
|
"""Send arm night command."""
|
||||||
|
if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT):
|
||||||
|
return
|
||||||
|
|
||||||
|
self._state = STATE_ALARM_ARMED_NIGHT
|
||||||
|
self._state_ts = dt_util.utcnow()
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
if self._pending_time:
|
||||||
|
track_point_in_time(
|
||||||
|
self._hass, self.async_update_ha_state,
|
||||||
|
self._state_ts + self._pending_time)
|
||||||
|
|
||||||
def alarm_trigger(self, code=None):
|
def alarm_trigger(self, code=None):
|
||||||
"""Send alarm trigger command. No code needed."""
|
"""Send alarm trigger command. No code needed."""
|
||||||
self._pre_trigger_state = self._state
|
self._pre_trigger_state = self._state
|
||||||
|
|
|
@ -31,6 +31,17 @@ alarm_arm_away:
|
||||||
description: An optional code to arm away the alarm control panel with
|
description: An optional code to arm away the alarm control panel with
|
||||||
example: 1234
|
example: 1234
|
||||||
|
|
||||||
|
alarm_arm_night:
|
||||||
|
description: Send the alarm the command for arm night
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of alarm control panel to arm night
|
||||||
|
example: 'alarm_control_panel.downstairs'
|
||||||
|
code:
|
||||||
|
description: An optional code to arm night the alarm control panel with
|
||||||
|
example: 1234
|
||||||
|
|
||||||
alarm_trigger:
|
alarm_trigger:
|
||||||
description: Send the alarm the command for trigger
|
description: Send the alarm the command for trigger
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@ import homeassistant.components.alarm_control_panel as alarm
|
||||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
|
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||||
CONF_NAME)
|
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
|
||||||
|
|
||||||
REQUIREMENTS = ['total_connect_client==0.11']
|
REQUIREMENTS = ['total_connect_client==0.11']
|
||||||
|
|
||||||
|
@ -74,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||||
state = STATE_ALARM_ARMED_HOME
|
state = STATE_ALARM_ARMED_HOME
|
||||||
elif status == self._client.ARMED_AWAY:
|
elif status == self._client.ARMED_AWAY:
|
||||||
state = STATE_ALARM_ARMED_AWAY
|
state = STATE_ALARM_ARMED_AWAY
|
||||||
|
elif status == self._client.ARMED_STAY_NIGHT:
|
||||||
|
state = STATE_ALARM_ARMED_NIGHT
|
||||||
|
elif status == self._client.ARMING:
|
||||||
|
state = STATE_ALARM_ARMING
|
||||||
|
elif status == self._client.DISARMING:
|
||||||
|
state = STATE_ALARM_DISARMING
|
||||||
else:
|
else:
|
||||||
state = STATE_UNKNOWN
|
state = STATE_UNKNOWN
|
||||||
|
|
||||||
|
@ -90,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||||
def alarm_arm_away(self, code=None):
|
def alarm_arm_away(self, code=None):
|
||||||
"""Send arm away command."""
|
"""Send arm away command."""
|
||||||
self._client.arm_away()
|
self._client.arm_away()
|
||||||
|
|
||||||
|
def alarm_arm_night(self, code=None):
|
||||||
|
"""Send arm night command."""
|
||||||
|
self._client.arm_stay_night()
|
||||||
|
|
|
@ -199,7 +199,10 @@ STATE_STANDBY = 'standby'
|
||||||
STATE_ALARM_DISARMED = 'disarmed'
|
STATE_ALARM_DISARMED = 'disarmed'
|
||||||
STATE_ALARM_ARMED_HOME = 'armed_home'
|
STATE_ALARM_ARMED_HOME = 'armed_home'
|
||||||
STATE_ALARM_ARMED_AWAY = 'armed_away'
|
STATE_ALARM_ARMED_AWAY = 'armed_away'
|
||||||
|
STATE_ALARM_ARMED_NIGHT = 'armed_night'
|
||||||
STATE_ALARM_PENDING = 'pending'
|
STATE_ALARM_PENDING = 'pending'
|
||||||
|
STATE_ALARM_ARMING = 'arming'
|
||||||
|
STATE_ALARM_DISARMING = 'disarming'
|
||||||
STATE_ALARM_TRIGGERED = 'triggered'
|
STATE_ALARM_TRIGGERED = 'triggered'
|
||||||
STATE_LOCKED = 'locked'
|
STATE_LOCKED = 'locked'
|
||||||
STATE_UNLOCKED = 'unlocked'
|
STATE_UNLOCKED = 'unlocked'
|
||||||
|
@ -347,6 +350,7 @@ SERVICE_SHUFFLE_SET = 'shuffle_set'
|
||||||
SERVICE_ALARM_DISARM = 'alarm_disarm'
|
SERVICE_ALARM_DISARM = 'alarm_disarm'
|
||||||
SERVICE_ALARM_ARM_HOME = 'alarm_arm_home'
|
SERVICE_ALARM_ARM_HOME = 'alarm_arm_home'
|
||||||
SERVICE_ALARM_ARM_AWAY = 'alarm_arm_away'
|
SERVICE_ALARM_ARM_AWAY = 'alarm_arm_away'
|
||||||
|
SERVICE_ALARM_ARM_NIGHT = 'alarm_arm_night'
|
||||||
SERVICE_ALARM_TRIGGER = 'alarm_trigger'
|
SERVICE_ALARM_TRIGGER = 'alarm_trigger'
|
||||||
|
|
||||||
SERVICE_LOCK = 'lock'
|
SERVICE_LOCK = 'lock'
|
||||||
|
|
|
@ -6,7 +6,7 @@ from unittest.mock import patch
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import setup_component
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
|
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
||||||
from homeassistant.components import alarm_control_panel
|
from homeassistant.components import alarm_control_panel
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
@ -182,6 +182,84 @@ class TestAlarmControlPanelManual(unittest.TestCase):
|
||||||
self.assertEqual(STATE_ALARM_DISARMED,
|
self.assertEqual(STATE_ALARM_DISARMED,
|
||||||
self.hass.states.get(entity_id).state)
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
def test_arm_night_no_pending(self):
|
||||||
|
"""Test arm night method."""
|
||||||
|
self.assertTrue(setup_component(
|
||||||
|
self.hass, alarm_control_panel.DOMAIN,
|
||||||
|
{'alarm_control_panel': {
|
||||||
|
'platform': 'manual',
|
||||||
|
'name': 'test',
|
||||||
|
'code': CODE,
|
||||||
|
'pending_time': 0,
|
||||||
|
'disarm_after_trigger': False
|
||||||
|
}}))
|
||||||
|
|
||||||
|
entity_id = 'alarm_control_panel.test'
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_DISARMED,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
alarm_control_panel.alarm_arm_night(self.hass, CODE)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_ARMED_NIGHT,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
def test_arm_night_with_pending(self):
|
||||||
|
"""Test arm night method."""
|
||||||
|
self.assertTrue(setup_component(
|
||||||
|
self.hass, alarm_control_panel.DOMAIN,
|
||||||
|
{'alarm_control_panel': {
|
||||||
|
'platform': 'manual',
|
||||||
|
'name': 'test',
|
||||||
|
'code': CODE,
|
||||||
|
'pending_time': 1,
|
||||||
|
'disarm_after_trigger': False
|
||||||
|
}}))
|
||||||
|
|
||||||
|
entity_id = 'alarm_control_panel.test'
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_DISARMED,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
alarm_control_panel.alarm_arm_night(self.hass, CODE, entity_id)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_PENDING,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
future = dt_util.utcnow() + timedelta(seconds=1)
|
||||||
|
with patch(('homeassistant.components.alarm_control_panel.manual.'
|
||||||
|
'dt_util.utcnow'), return_value=future):
|
||||||
|
fire_time_changed(self.hass, future)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_ARMED_NIGHT,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
def test_arm_night_with_invalid_code(self):
|
||||||
|
"""Attempt to night home without a valid code."""
|
||||||
|
self.assertTrue(setup_component(
|
||||||
|
self.hass, alarm_control_panel.DOMAIN,
|
||||||
|
{'alarm_control_panel': {
|
||||||
|
'platform': 'manual',
|
||||||
|
'name': 'test',
|
||||||
|
'code': CODE,
|
||||||
|
'pending_time': 1,
|
||||||
|
'disarm_after_trigger': False
|
||||||
|
}}))
|
||||||
|
|
||||||
|
entity_id = 'alarm_control_panel.test'
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_DISARMED,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
|
alarm_control_panel.alarm_arm_night(self.hass, CODE + '2')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(STATE_ALARM_DISARMED,
|
||||||
|
self.hass.states.get(entity_id).state)
|
||||||
|
|
||||||
def test_trigger_no_pending(self):
|
def test_trigger_no_pending(self):
|
||||||
"""Test triggering when no pending submitted method."""
|
"""Test triggering when no pending submitted method."""
|
||||||
self.assertTrue(setup_component(
|
self.assertTrue(setup_component(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue