From fc946da5db3e9d8ea8bbd32ff06635fae4c11180 Mon Sep 17 00:00:00 2001 From: sfam Date: Fri, 18 Sep 2015 15:30:34 +0000 Subject: [PATCH 1/3] Add MQTT alarm --- .../components/alarm_control_panel/mqtt.py | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 homeassistant/components/alarm_control_panel/mqtt.py diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py new file mode 100644 index 00000000000..3317687f2fd --- /dev/null +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -0,0 +1,147 @@ +""" +homeassistant.components.alarm_control_panel.mqtt + +This platform enables the possibility to control a MQTT alarm. +In this platform, 'state_topic' and 'command_topic' are required. +The alarm will only change state after receiving the a new state +from 'state_topic'. If these messages are published with RETAIN flag, +the MQTT alarm will receive an instant state update after subscription +and will start with correct state. Otherwise, the initial state will +be 'unknown'. + +Configuration: + +alarm_control_panel: + platform: mqtt + name: "MQTT Alarm" + state_topic: "home/alarm" + command_topic: "home/alarm/set" + qos: 0 + payload_disarm: "DISARM" + payload_arm_home: "ARM_HOME" + payload_arm_away: "ARM_AWAY" + +Variables: + +name +*Optional +The name of the alarm. Default is 'MQTT Alarm'. + +state_topic +*Required +The MQTT topic subscribed to receive state updates. + +command_topic +*Required +The MQTT topic to publish commands to change the alarm state. + +qos +*Optional +The maximum QoS level of the state topic. Default is 0. +This QoS will also be used to publishing messages. + +payload_disarm +*Optional +The payload do disarm alarm. Default is "DISARM". + +payload_arm_home +*Optional +The payload to set armed-home mode. Default is "ARM_HOME". + +payload_arm_away +*Optional +The payload to set armed-away mode. Default is "ARM_AWAY". + +""" + +import logging +import homeassistant.components.mqtt as mqtt +import homeassistant.components.alarm_control_panel as alarm + +from homeassistant.const import (STATE_UNKNOWN) + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = "MQTT Alarm" +DEFAULT_QOS = 0 +DEFAULT_PAYLOAD_DISARM = "DISARM" +DEFAULT_PAYLOAD_ARM_HOME = "ARM_HOME" +DEFAULT_PAYLOAD_ARM_AWAY = "ARM_AWAY" + +DEPENDENCIES = ['mqtt'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the MQTT platform. """ + + if config.get('state_topic') is None: + _LOGGER.error("Missing required variable: state_topic") + return False + + if config.get('command_topic') is None: + _LOGGER.error("Missing required variable: command_topic") + return False + + add_devices([MqttAlarm( + hass, + config.get('name', DEFAULT_NAME), + config.get('state_topic'), + config.get('command_topic'), + config.get('qos', DEFAULT_QOS), + config.get('payload_disarm', DEFAULT_PAYLOAD_DISARM), + config.get('payload_arm_home', DEFAULT_PAYLOAD_ARM_HOME), + config.get('payload_arm_away', DEFAULT_PAYLOAD_ARM_AWAY))]) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class MqttAlarm(alarm.AlarmControlPanel): + """ represents a MQTT alarm status within home assistant. """ + + def __init__(self, hass, name, state_topic, command_topic, qos, + payload_disarm, payload_arm_home, payload_arm_away): + self._state = STATE_UNKNOWN + self._hass = hass + self._name = name + self._state_topic = state_topic + self._command_topic = command_topic + self._qos = qos + self._payload_disarm = payload_disarm + self._payload_arm_home = payload_arm_home + self._payload_arm_away = payload_arm_away + + def message_received(topic, payload, qos): + """ A new MQTT message has been received. """ + self._state = payload + self.update_ha_state() + + mqtt.subscribe(hass, self._state_topic, message_received, self._qos) + + @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 + + def alarm_disarm(self, code): + """ Send disarm command. """ + mqtt.publish(self.hass, self._command_topic, self._payload_disarm, + self._qos) + + def alarm_arm_home(self, code): + """ Send arm home command. """ + mqtt.publish(self.hass, self._command_topic, self._payload_arm_home, + self._qos) + + def alarm_arm_away(self, code): + """ Send arm away command. """ + mqtt.publish(self.hass, self._command_topic, self._payload_arm_away, + self._qos) From 35eed93443ed59085995b7dd811f9af09076b3b6 Mon Sep 17 00:00:00 2001 From: sfam Date: Sat, 19 Sep 2015 17:32:37 +0000 Subject: [PATCH 2/3] add a requires_code property on alarm object --- .../components/alarm_control_panel/__init__.py | 13 ++++++++++--- .../components/alarm_control_panel/mqtt.py | 11 ++++++++--- .../components/alarm_control_panel/verisure.py | 11 ++++++++--- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index bf68e35ffe3..a74f268b632 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -93,16 +93,23 @@ def alarm_arm_away(hass, code, entity_id=None): hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data) +# pylint: disable=no-self-use class AlarmControlPanel(Entity): """ ABC for alarm control devices. """ - def alarm_disarm(self, code): + + @property + def requires_code(self): + """ Boolean if alarm requires a code """ + return None + + def alarm_disarm(self, code=None): """ Send disarm command. """ raise NotImplementedError() - def alarm_arm_home(self, code): + def alarm_arm_home(self, code=None): """ Send arm home command. """ raise NotImplementedError() - def alarm_arm_away(self, code): + def alarm_arm_away(self, code=None): """ Send arm away command. """ raise NotImplementedError() diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index 3317687f2fd..d35d00d3ecd 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -131,17 +131,22 @@ class MqttAlarm(alarm.AlarmControlPanel): """ Returns the state of the device. """ return self._state - def alarm_disarm(self, code): + @property + def requires_code(self): + """ code is not required """ + return False + + def alarm_disarm(self, code=None): """ Send disarm command. """ mqtt.publish(self.hass, self._command_topic, self._payload_disarm, self._qos) - def alarm_arm_home(self, code): + def alarm_arm_home(self, code=None): """ Send arm home command. """ mqtt.publish(self.hass, self._command_topic, self._payload_arm_home, self._qos) - def alarm_arm_away(self, code): + def alarm_arm_away(self, code=None): """ Send arm away command. """ mqtt.publish(self.hass, self._command_topic, self._payload_arm_away, self._qos) diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index f19cdc102d2..9889223a0aa 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -51,6 +51,11 @@ class VerisureAlarm(alarm.AlarmControlPanel): """ Returns the state of the device. """ return self._state + @property + def requires_code(self): + """ code is required """ + return True + def update(self): ''' update alarm status ''' verisure.update() @@ -66,21 +71,21 @@ class VerisureAlarm(alarm.AlarmControlPanel): 'Unknown alarm state %s', verisure.STATUS[self._device][self._id].status) - def alarm_disarm(self, code): + def alarm_disarm(self, code=None): """ Send disarm command. """ verisure.MY_PAGES.set_alarm_status( code, verisure.MY_PAGES.ALARM_DISARMED) _LOGGER.warning('disarming') - def alarm_arm_home(self, code): + def alarm_arm_home(self, code=None): """ Send arm home command. """ verisure.MY_PAGES.set_alarm_status( code, verisure.MY_PAGES.ALARM_ARMED_HOME) _LOGGER.warning('arming home') - def alarm_arm_away(self, code): + def alarm_arm_away(self, code=None): """ Send arm away command. """ verisure.MY_PAGES.set_alarm_status( code, From e29deb0202bda22ed9cd75f25f469a9264e2c985 Mon Sep 17 00:00:00 2001 From: sfam Date: Sat, 19 Sep 2015 22:22:37 +0000 Subject: [PATCH 3/3] add a code_format property on alarm object and a optional code for its MQTT platform --- .../alarm_control_panel/__init__.py | 5 +++ .../components/alarm_control_panel/mqtt.py | 41 ++++++++++++++----- .../alarm_control_panel/verisure.py | 5 +++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index a74f268b632..dab26a47a62 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -102,6 +102,11 @@ class AlarmControlPanel(Entity): """ Boolean if alarm requires a code """ return None + @property + def code_format(self): + """ regex for code format """ + return None + def alarm_disarm(self, code=None): """ Send disarm command. """ raise NotImplementedError() diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index d35d00d3ecd..69dba6379cb 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -20,6 +20,7 @@ alarm_control_panel: payload_disarm: "DISARM" payload_arm_home: "ARM_HOME" payload_arm_away: "ARM_AWAY" + code: "mySecretCode" Variables: @@ -52,6 +53,10 @@ payload_arm_away *Optional The payload to set armed-away mode. Default is "ARM_AWAY". +code +*Optional +If defined, specifies a code to enable or disable the alarm in the frontend. + """ import logging @@ -90,7 +95,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): config.get('qos', DEFAULT_QOS), config.get('payload_disarm', DEFAULT_PAYLOAD_DISARM), config.get('payload_arm_home', DEFAULT_PAYLOAD_ARM_HOME), - config.get('payload_arm_away', DEFAULT_PAYLOAD_ARM_AWAY))]) + config.get('payload_arm_away', DEFAULT_PAYLOAD_ARM_AWAY), + config.get('code'))]) # pylint: disable=too-many-arguments, too-many-instance-attributes @@ -98,7 +104,7 @@ class MqttAlarm(alarm.AlarmControlPanel): """ represents a MQTT alarm status within home assistant. """ def __init__(self, hass, name, state_topic, command_topic, qos, - payload_disarm, payload_arm_home, payload_arm_away): + payload_disarm, payload_arm_home, payload_arm_away, code): self._state = STATE_UNKNOWN self._hass = hass self._name = name @@ -108,6 +114,7 @@ class MqttAlarm(alarm.AlarmControlPanel): self._payload_disarm = payload_disarm self._payload_arm_home = payload_arm_home self._payload_arm_away = payload_arm_away + self._code = code def message_received(topic, payload, qos): """ A new MQTT message has been received. """ @@ -133,20 +140,34 @@ class MqttAlarm(alarm.AlarmControlPanel): @property def requires_code(self): - """ code is not required """ - return False + """ code is required if it's defined in configuration file """ + return self._code is not None + + @property + def code_format(self): + """ One or more characters """ + return '.+' def alarm_disarm(self, code=None): """ Send disarm command. """ - mqtt.publish(self.hass, self._command_topic, self._payload_disarm, - self._qos) + if code == str(self._code) or not self.requires_code: + mqtt.publish(self.hass, self._command_topic, + self._payload_disarm, self._qos) + else: + _LOGGER.warning("Wrong code entered while disarming!") def alarm_arm_home(self, code=None): """ Send arm home command. """ - mqtt.publish(self.hass, self._command_topic, self._payload_arm_home, - self._qos) + if code == str(self._code) or not self.requires_code: + mqtt.publish(self.hass, self._command_topic, + self._payload_arm_home, self._qos) + else: + _LOGGER.warning("Wrong code entered while arming home!") def alarm_arm_away(self, code=None): """ Send arm away command. """ - mqtt.publish(self.hass, self._command_topic, self._payload_arm_away, - self._qos) + if code == str(self._code) or not self.requires_code: + mqtt.publish(self.hass, self._command_topic, + self._payload_arm_away, self._qos) + else: + _LOGGER.warning("Wrong code entered while arming away!") diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index 9889223a0aa..38b5fbe2b12 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -56,6 +56,11 @@ class VerisureAlarm(alarm.AlarmControlPanel): """ code is required """ return True + @property + def code_format(self): + """ Four digit code """ + return '[0-9]{4}' + def update(self): ''' update alarm status ''' verisure.update()