diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index b85305b6d18..d3289e08e62 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -8,7 +8,7 @@ import os from homeassistant.components import verisure from homeassistant.const import ( - ATTR_ENTITY_ID, + ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity @@ -29,6 +29,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_TRIGGER: 'alarm_trigger' } ATTR_CODE = 'code' @@ -53,9 +54,9 @@ def setup(hass, config): target_alarms = component.extract_from_service(service) if ATTR_CODE not in service.data: - return - - code = service.data[ATTR_CODE] + code = None + else: + code = service.data[ATTR_CODE] method = SERVICE_TO_METHOD[service.service] @@ -72,36 +73,50 @@ def setup(hass, config): return True -def alarm_disarm(hass, code, entity_id=None): +def alarm_disarm(hass, code=None, entity_id=None): """ Send the alarm the command for disarm. """ - data = {ATTR_CODE: code} - + data = {} + if code: + data[ATTR_CODE] = code if entity_id: data[ATTR_ENTITY_ID] = entity_id hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data) -def alarm_arm_home(hass, code, entity_id=None): +def alarm_arm_home(hass, code=None, entity_id=None): """ Send the alarm the command for arm home. """ - data = {ATTR_CODE: code} - + data = {} + if code: + data[ATTR_CODE] = code if entity_id: data[ATTR_ENTITY_ID] = entity_id hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data) -def alarm_arm_away(hass, code, entity_id=None): +def alarm_arm_away(hass, code=None, entity_id=None): """ Send the alarm the command for arm away. """ - data = {ATTR_CODE: code} - + data = {} + if code: + data[ATTR_CODE] = code if entity_id: data[ATTR_ENTITY_ID] = entity_id hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data) +def alarm_trigger(hass, code=None, entity_id=None): + """ Send the alarm the command for trigger. """ + data = {} + if code: + data[ATTR_CODE] = code + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data) + + # pylint: disable=no-self-use class AlarmControlPanel(Entity): """ ABC for alarm control devices. """ @@ -123,6 +138,10 @@ class AlarmControlPanel(Entity): """ Send arm away command. """ raise NotImplementedError() + def alarm_trigger(self, code=None): + """ Send alarm trigger command. """ + raise NotImplementedError() + @property def state_attributes(self): """ Return the state attributes. """ diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py new file mode 100644 index 00000000000..eaeda59719b --- /dev/null +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -0,0 +1,167 @@ +""" +homeassistant.components.alarm_control_panel.manual + +Configuration: + +alarm_control_panel: + platform: manual + name: "HA Alarm" + code: "mySecretCode" + pending_time: 60 + trigger_time: 120 + +Variables: + +name +*Optional +The name of the alarm. Default is 'HA Alarm'. + +code +*Optional +If defined, specifies a code to arm or disarm the alarm in the frontend. + +pending_time +*Optional +The time in seconds of the pending time before arming the alarm. +Default is 60 seconds. + +trigger_time +*Optional +The time in seconds of the trigger time in which the alarm is firing. +Default is 120 seconds. + +""" + +import logging +import datetime +import homeassistant.components.alarm_control_panel as alarm +from homeassistant.helpers.event import track_point_in_time +import homeassistant.util.dt as dt_util + +from homeassistant.const import ( + STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = [] + +DEFAULT_ALARM_NAME = 'HA Alarm' +DEFAULT_PENDING_TIME = 60 +DEFAULT_TRIGGER_TIME = 120 + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the manual alarm platform. """ + + add_devices([ManualAlarm( + hass, + config.get('name', DEFAULT_ALARM_NAME), + config.get('code'), + config.get('pending_time', DEFAULT_PENDING_TIME), + config.get('trigger_time', DEFAULT_TRIGGER_TIME), + )]) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +# pylint: disable=abstract-method +class ManualAlarm(alarm.AlarmControlPanel): + """ represents an alarm status within home assistant. """ + + def __init__(self, hass, name, code, pending_time, trigger_time): + self._state = STATE_ALARM_DISARMED + self._hass = hass + self._name = name + self._code = code + self._pending_time = datetime.timedelta(seconds=pending_time) + self._trigger_time = datetime.timedelta(seconds=trigger_time) + self._state_ts = None + self._pending_to = None + + @property + def should_poll(self): + """ No polling needed """ + return False + + @property + def name(self): + """ Returns the name of the device. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def code_format(self): + """ One or more characters """ + return None if self._code is None else '.+' + + def update_state(self, state, pending_to): + """ changes between state with delay """ + self._state = state + self._state_ts = dt_util.utcnow() + self._pending_to = pending_to + self.update_ha_state() + + def alarm_disarm(self, code=None): + """ Send disarm command. """ + if code == str(self._code) or self.code_format is None: + self.update_state(STATE_ALARM_DISARMED, None) + else: + _LOGGER.warning("Wrong code entered while disarming!") + + def alarm_arm_home(self, code=None): + """ Send arm home command. """ + if code == str(self._code) or self.code_format is None: + self.update_state(STATE_ALARM_PENDING, STATE_ALARM_ARMED_HOME) + + def delayed_alarm_arm_home(event_time): + """ callback for delayed action """ + if self._pending_to == STATE_ALARM_ARMED_HOME and \ + dt_util.utcnow() - self._state_ts >= self._pending_time: + self.update_state(STATE_ALARM_ARMED_HOME, None) + track_point_in_time( + self._hass, delayed_alarm_arm_home, + dt_util.utcnow() + self._pending_time) + else: + _LOGGER.warning("Wrong code entered while arming home!") + + def alarm_arm_away(self, code=None): + """ Send arm away command. """ + if code == str(self._code) or self.code_format is None: + self.update_state(STATE_ALARM_PENDING, STATE_ALARM_ARMED_AWAY) + + def delayed_alarm_arm_away(event_time): + """ callback for delayed action """ + if self._pending_to == STATE_ALARM_ARMED_AWAY and \ + dt_util.utcnow() - self._state_ts >= self._pending_time: + self.update_state(STATE_ALARM_ARMED_AWAY, None) + track_point_in_time( + self._hass, delayed_alarm_arm_away, + dt_util.utcnow() + self._pending_time) + else: + _LOGGER.warning("Wrong code entered while arming away!") + + def alarm_trigger(self, code=None): + """ Send alarm trigger command. No code needed. """ + self.update_state(STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) + + def delayed_alarm_trigger(event_time): + """ callback for delayed action """ + if self._pending_to == STATE_ALARM_TRIGGERED and \ + dt_util.utcnow() - self._state_ts >= self._pending_time: + self.update_state(STATE_ALARM_TRIGGERED, STATE_ALARM_DISARMED) + + def delayed_alarm_disarm(event_time): + """ callback for delayed action """ + if self._pending_to == STATE_ALARM_DISARMED and \ + dt_util.utcnow() - self._state_ts >= self._trigger_time: + self.update_state(STATE_ALARM_DISARMED, None) + track_point_in_time( + self._hass, delayed_alarm_disarm, + dt_util.utcnow() + self._trigger_time) + track_point_in_time( + self._hass, delayed_alarm_trigger, + dt_util.utcnow() + self._pending_time) diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index c04c8ee6031..6005a307fbd 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -99,6 +99,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes +# pylint: disable=abstract-method class MqttAlarm(alarm.AlarmControlPanel): """ represents a MQTT alarm status within home assistant. """ diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index c7c24a60c4a..9e0475592bd 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -33,6 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(alarms) +# pylint: disable=abstract-method class VerisureAlarm(alarm.AlarmControlPanel): """ Represents a Verisure alarm status. """ diff --git a/homeassistant/const.py b/homeassistant/const.py index 278ffea218a..64b88785fce 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -51,6 +51,8 @@ STATE_STANDBY = 'standby' STATE_ALARM_DISARMED = 'disarmed' STATE_ALARM_ARMED_HOME = 'armed_home' STATE_ALARM_ARMED_AWAY = 'armed_away' +STATE_ALARM_PENDING = 'pending' +STATE_ALARM_TRIGGERED = 'triggered' # #### STATE AND EVENT ATTRIBUTES #### # Contains current time for a TIME_CHANGED event @@ -128,6 +130,7 @@ SERVICE_MEDIA_SEEK = "media_seek" SERVICE_ALARM_DISARM = "alarm_disarm" SERVICE_ALARM_ARM_HOME = "alarm_arm_home" SERVICE_ALARM_ARM_AWAY = "alarm_arm_away" +SERVICE_ALARM_TRIGGER = "alarm_trigger" # #### API / REMOTE #### SERVER_PORT = 8123