From e57b3ae847a6d12c571b4f1eae1bbd49550a6479 Mon Sep 17 00:00:00 2001 From: sfam Date: Mon, 28 Sep 2015 23:36:46 +0000 Subject: [PATCH 1/2] add manual alarm --- .../alarm_control_panel/__init__.py | 23 ++- .../components/alarm_control_panel/manual.py | 142 ++++++++++++++++++ .../components/alarm_control_panel/mqtt.py | 4 + .../alarm_control_panel/verisure.py | 4 + homeassistant/const.py | 3 + 5 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/alarm_control_panel/manual.py diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 49eb5eafba7..f922ecdacc0 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] @@ -102,6 +103,16 @@ def alarm_arm_away(hass, code, entity_id=None): hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data) +def alarm_trigger(hass, code, entity_id=None): + """ Send the alarm the command for trigger. """ + 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 +134,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..e83a2e9da2f --- /dev/null +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -0,0 +1,142 @@ +""" +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 time +import homeassistant.components.alarm_control_panel as alarm + +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 +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 = pending_time + self._trigger_time = trigger_time + 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 change_alarm_state(self, begin, end, delay=0): + """ changes between state with delay """ + self._state = begin + self._pending_to = end + self.update_ha_state() + time.sleep(delay) + if self._pending_to == end and begin != end: + self._state = end + self._pending_to = None + 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.change_alarm_state( + STATE_ALARM_DISARMED, STATE_ALARM_DISARMED) + 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.change_alarm_state( + STATE_ALARM_PENDING, STATE_ALARM_ARMED_HOME, + 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.change_alarm_state( + STATE_ALARM_PENDING, STATE_ALARM_ARMED_AWAY, + 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.change_alarm_state( + STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + self._pending_time) + if self._state == STATE_ALARM_TRIGGERED: + self.change_alarm_state( + STATE_ALARM_TRIGGERED, STATE_ALARM_DISARMED, + self._trigger_time) diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index f4d89e5a847..27e78cb38fe 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -166,3 +166,7 @@ class MqttAlarm(alarm.AlarmControlPanel): self._payload_arm_away, self._qos) else: _LOGGER.warning("Wrong code entered while arming away!") + + def alarm_trigger(self, code=None): + """ Send alarm trigger command. No code needed. """ + return diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index 7923f7c97e7..f590d462e9b 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -91,3 +91,7 @@ class VerisureAlarm(alarm.AlarmControlPanel): code, verisure.MY_PAGES.ALARM_ARMED_AWAY) _LOGGER.warning('arming away') + + def alarm_trigger(self, code=None): + """ Send alarm trigger command. No code needed. """ + return diff --git a/homeassistant/const.py b/homeassistant/const.py index 5fc3ad4f4a9..7df5e27fbe7 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 @@ -121,6 +123,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 From 1b7ce2146c21262d2a0c2eda7569ce980ba426d9 Mon Sep 17 00:00:00 2001 From: sfam Date: Tue, 13 Oct 2015 00:56:24 +0000 Subject: [PATCH 2/2] replace sleeps with track_point_in_time --- .../alarm_control_panel/__init__.py | 28 ++++--- .../components/alarm_control_panel/manual.py | 77 ++++++++++++------- .../components/alarm_control_panel/mqtt.py | 5 +- .../alarm_control_panel/verisure.py | 5 +- 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index f922ecdacc0..802faaac958 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -73,40 +73,44 @@ 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, entity_id=None): +def alarm_trigger(hass, code=None, entity_id=None): """ Send the alarm the command for trigger. """ - data = {ATTR_CODE: code} - + data = {} + if code: + data[ATTR_CODE] = code if entity_id: data[ATTR_ENTITY_ID] = entity_id diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index e83a2e9da2f..eaeda59719b 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -33,8 +33,10 @@ Default is 120 seconds. """ import logging -import time +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, @@ -62,6 +64,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 ManualAlarm(alarm.AlarmControlPanel): """ represents an alarm status within home assistant. """ @@ -70,8 +73,9 @@ class ManualAlarm(alarm.AlarmControlPanel): self._hass = hass self._name = name self._code = code - self._pending_time = pending_time - self._trigger_time = trigger_time + 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 @@ -94,49 +98,70 @@ class ManualAlarm(alarm.AlarmControlPanel): """ One or more characters """ return None if self._code is None else '.+' - def change_alarm_state(self, begin, end, delay=0): + def update_state(self, state, pending_to): """ changes between state with delay """ - self._state = begin - self._pending_to = end + self._state = state + self._state_ts = dt_util.utcnow() + self._pending_to = pending_to self.update_ha_state() - time.sleep(delay) - if self._pending_to == end and begin != end: - self._state = end - self._pending_to = None - 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.change_alarm_state( - STATE_ALARM_DISARMED, STATE_ALARM_DISARMED) + 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.change_alarm_state( - STATE_ALARM_PENDING, STATE_ALARM_ARMED_HOME, - self._pending_time) + 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.change_alarm_state( - STATE_ALARM_PENDING, STATE_ALARM_ARMED_AWAY, - self._pending_time) + 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.change_alarm_state( - STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, - self._pending_time) - if self._state == STATE_ALARM_TRIGGERED: - self.change_alarm_state( - STATE_ALARM_TRIGGERED, STATE_ALARM_DISARMED, - self._trigger_time) + 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 27e78cb38fe..cd71e223481 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -100,6 +100,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. """ @@ -166,7 +167,3 @@ class MqttAlarm(alarm.AlarmControlPanel): self._payload_arm_away, self._qos) else: _LOGGER.warning("Wrong code entered while arming away!") - - def alarm_trigger(self, code=None): - """ Send alarm trigger command. No code needed. """ - return diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index f590d462e9b..b2c10e6114e 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 within home assistant. """ @@ -91,7 +92,3 @@ class VerisureAlarm(alarm.AlarmControlPanel): code, verisure.MY_PAGES.ALARM_ARMED_AWAY) _LOGGER.warning('arming away') - - def alarm_trigger(self, code=None): - """ Send alarm trigger command. No code needed. """ - return