diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 39c86f3215f..005048ba8c1 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.const import ( 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.loader import bind_hass from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa @@ -31,6 +32,7 @@ SERVICE_TO_METHOD = { SERVICE_ALARM_DISARM: 'alarm_disarm', SERVICE_ALARM_ARM_HOME: 'alarm_arm_home', SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away', + SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night', 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) +@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 def alarm_trigger(hass, code=None, entity_id=None): """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) + 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): """Send alarm trigger command.""" raise NotImplementedError() diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index c87aea862d5..97820ab4b2b 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -12,9 +12,10 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm import homeassistant.util.dt as dt_util from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, - STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, - CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER) + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, + CONF_DISARM_AFTER_TRIGGER) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_time @@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel): def state(self): """Return the state of the device.""" 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 > \ dt_util.utcnow(): return STATE_ALARM_PENDING @@ -145,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel): self._hass, self.async_update_ha_state, 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): """Send alarm trigger command. No code needed.""" self._pre_trigger_state = self._state diff --git a/homeassistant/components/alarm_control_panel/services.yaml b/homeassistant/components/alarm_control_panel/services.yaml index 6cc3946ca66..19c3ca0233d 100644 --- a/homeassistant/components/alarm_control_panel/services.yaml +++ b/homeassistant/components/alarm_control_panel/services.yaml @@ -31,6 +31,17 @@ alarm_arm_away: description: An optional code to arm away the alarm control panel with 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: description: Send the alarm the command for trigger diff --git a/homeassistant/components/alarm_control_panel/totalconnect.py b/homeassistant/components/alarm_control_panel/totalconnect.py index 9c0b5108fee..05dc8aeef20 100644 --- a/homeassistant/components/alarm_control_panel/totalconnect.py +++ b/homeassistant/components/alarm_control_panel/totalconnect.py @@ -13,8 +13,8 @@ import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, - CONF_NAME) + STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, + STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME) REQUIREMENTS = ['total_connect_client==0.11'] @@ -74,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel): state = STATE_ALARM_ARMED_HOME elif status == self._client.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: state = STATE_UNKNOWN @@ -90,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel): def alarm_arm_away(self, code=None): """Send arm away command.""" self._client.arm_away() + + def alarm_arm_night(self, code=None): + """Send arm night command.""" + self._client.arm_stay_night() diff --git a/homeassistant/const.py b/homeassistant/const.py index 978dafa457b..19ce7e470c2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -199,7 +199,10 @@ STATE_STANDBY = 'standby' STATE_ALARM_DISARMED = 'disarmed' STATE_ALARM_ARMED_HOME = 'armed_home' STATE_ALARM_ARMED_AWAY = 'armed_away' +STATE_ALARM_ARMED_NIGHT = 'armed_night' STATE_ALARM_PENDING = 'pending' +STATE_ALARM_ARMING = 'arming' +STATE_ALARM_DISARMING = 'disarming' STATE_ALARM_TRIGGERED = 'triggered' STATE_LOCKED = 'locked' STATE_UNLOCKED = 'unlocked' @@ -347,6 +350,7 @@ SERVICE_SHUFFLE_SET = 'shuffle_set' SERVICE_ALARM_DISARM = 'alarm_disarm' SERVICE_ALARM_ARM_HOME = 'alarm_arm_home' SERVICE_ALARM_ARM_AWAY = 'alarm_arm_away' +SERVICE_ALARM_ARM_NIGHT = 'alarm_arm_night' SERVICE_ALARM_TRIGGER = 'alarm_trigger' SERVICE_LOCK = 'lock' diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 7bd89d12a0a..e5d819bc815 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -6,7 +6,7 @@ from unittest.mock import patch from homeassistant.setup import setup_component from homeassistant.const import ( 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 import homeassistant.util.dt as dt_util @@ -182,6 +182,84 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.assertEqual(STATE_ALARM_DISARMED, 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): """Test triggering when no pending submitted method.""" self.assertTrue(setup_component(