diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py new file mode 100644 index 00000000000..bf68e35ffe3 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -0,0 +1,108 @@ +""" +homeassistant.components.alarm_control_panel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Component to interface with a alarm control panel. +""" +import logging +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.components import verisure +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) + +DOMAIN = 'alarm_control_panel' +DEPENDENCIES = [] +SCAN_INTERVAL = 30 + +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +# Maps discovered services to their platforms +DISCOVERY_PLATFORMS = { + verisure.DISCOVER_SENSORS: 'verisure' +} + +SERVICE_TO_METHOD = { + SERVICE_ALARM_DISARM: 'alarm_disarm', + SERVICE_ALARM_ARM_HOME: 'alarm_arm_home', + SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away', +} + +ATTR_CODE = 'code' + +ATTR_TO_PROPERTY = [ + ATTR_CODE, +] + + +def setup(hass, config): + """ Track states and offer events for sensors. """ + component = EntityComponent( + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, + DISCOVERY_PLATFORMS) + + component.setup(config) + + def alarm_service_handler(service): + """ Maps services to methods on Alarm. """ + target_alarms = component.extract_from_service(service) + + if ATTR_CODE not in service.data: + return + + code = service.data[ATTR_CODE] + + method = SERVICE_TO_METHOD[service.service] + + for alarm in target_alarms: + getattr(alarm, method)(code) + + for service in SERVICE_TO_METHOD: + hass.services.register(DOMAIN, service, alarm_service_handler) + + return True + + +def alarm_disarm(hass, code, entity_id=None): + """ Send the alarm the command for disarm. """ + 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): + """ Send the alarm the command for arm home. """ + 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): + """ Send the alarm the command for arm away. """ + data = {ATTR_CODE: code} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data) + + +class AlarmControlPanel(Entity): + """ ABC for alarm control devices. """ + def alarm_disarm(self, code): + """ Send disarm command. """ + raise NotImplementedError() + + def alarm_arm_home(self, code): + """ Send arm home command. """ + raise NotImplementedError() + + def alarm_arm_away(self, code): + """ Send arm away command. """ + raise NotImplementedError() diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py new file mode 100644 index 00000000000..f19cdc102d2 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -0,0 +1,88 @@ +""" +homeassistant.components.alarm_control_panel.verisure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Interfaces with Verisure alarm control panel. +""" +import logging + +import homeassistant.components.verisure as verisure +import homeassistant.components.alarm_control_panel as alarm + +from homeassistant.const import ( + STATE_UNKNOWN, + STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Verisure platform. """ + + if not verisure.MY_PAGES: + _LOGGER.error('A connection has not been made to Verisure mypages.') + return False + + alarms = [] + + alarms.extend([ + VerisureAlarm(value) + for value in verisure.get_alarm_status().values() + if verisure.SHOW_ALARM + ]) + + add_devices(alarms) + + +class VerisureAlarm(alarm.AlarmControlPanel): + """ represents a Verisure alarm status within home assistant. """ + + def __init__(self, alarm_status): + self._id = alarm_status.id + self._device = verisure.MY_PAGES.DEVICE_ALARM + self._state = STATE_UNKNOWN + + @property + def name(self): + """ Returns the name of the device. """ + return 'Alarm {}'.format(self._id) + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + def update(self): + ''' update alarm status ''' + verisure.update() + + if verisure.STATUS[self._device][self._id].status == 'unarmed': + self._state = STATE_ALARM_DISARMED + elif verisure.STATUS[self._device][self._id].status == 'armedhome': + self._state = STATE_ALARM_ARMED_HOME + elif verisure.STATUS[self._device][self._id].status == 'armedaway': + self._state = STATE_ALARM_ARMED_AWAY + elif verisure.STATUS[self._device][self._id].status != 'pending': + _LOGGER.error( + 'Unknown alarm state %s', + verisure.STATUS[self._device][self._id].status) + + def alarm_disarm(self, code): + """ Send disarm command. """ + verisure.MY_PAGES.set_alarm_status( + code, + verisure.MY_PAGES.ALARM_DISARMED) + _LOGGER.warning('disarming') + + def alarm_arm_home(self, code): + """ 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): + """ Send arm away command. """ + verisure.MY_PAGES.set_alarm_status( + code, + verisure.MY_PAGES.ALARM_ARMED_AWAY) + _LOGGER.warning('arming away') diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 61af1089775..47efa197870 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -36,12 +36,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hasattr(value, 'humidity') and value.humidity ]) - sensors.extend([ - VerisureAlarm(value) - for value in verisure.get_alarm_status().values() - if verisure.SHOW_ALARM - ]) - add_devices(sensors) @@ -103,25 +97,3 @@ class VerisureHygrometer(Entity): def update(self): ''' update sensor ''' verisure.update() - - -class VerisureAlarm(Entity): - """ represents a Verisure alarm status within home assistant. """ - - def __init__(self, alarm_status): - self._id = alarm_status.id - self._device = verisure.MY_PAGES.DEVICE_ALARM - - @property - def name(self): - """ Returns the name of the device. """ - return 'Alarm {}'.format(self._id) - - @property - def state(self): - """ Returns the state of the device. """ - return verisure.STATUS[self._device][self._id].label - - def update(self): - ''' update sensor ''' - verisure.update() diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index c7bc7c205e8..50fd9d6c7a9 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -59,8 +59,9 @@ from homeassistant.const import ( DOMAIN = "verisure" DISCOVER_SENSORS = 'verisure.sensors' DISCOVER_SWITCHES = 'verisure.switches' +DISCOVER_ALARMS = 'verisure.alarm_control_panel' -DEPENDENCIES = [] +DEPENDENCIES = ['alarm_control_panel'] REQUIREMENTS = [ 'https://github.com/persandstrom/python-verisure/archive/' '9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6' @@ -123,7 +124,8 @@ def setup(hass, config): # Load components for the devices in the ISY controller that we support for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), - ('switch', DISCOVER_SWITCHES))): + ('switch', DISCOVER_SWITCHES), + ('alarm_control_panel', DISCOVER_ALARMS))): component = get_component(comp_name) _LOGGER.info(config[DOMAIN]) bootstrap.setup_component(hass, component.DOMAIN, config) @@ -166,7 +168,7 @@ def reconnect(): def update(): """ Updates the status of verisure components. """ if WRONG_PASSWORD_GIVEN: - # Is there any way to inform user? + _LOGGER.error('Wrong password') return try: diff --git a/homeassistant/const.py b/homeassistant/const.py index 0603e390289..21a2e6c41e3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -47,6 +47,9 @@ STATE_PLAYING = 'playing' STATE_PAUSED = 'paused' STATE_IDLE = 'idle' STATE_STANDBY = 'standby' +STATE_ALARM_DISARMED = 'disarmed' +STATE_ALARM_ARMED_HOME = 'armed_home' +STATE_ALARM_ARMED_AWAY = 'armed_away' # #### STATE AND EVENT ATTRIBUTES #### # Contains current time for a TIME_CHANGED event @@ -114,6 +117,10 @@ SERVICE_MEDIA_NEXT_TRACK = "media_next_track" SERVICE_MEDIA_PREVIOUS_TRACK = "media_previous_track" SERVICE_MEDIA_SEEK = "media_seek" +SERVICE_ALARM_DISARM = "alarm_disarm" +SERVICE_ALARM_ARM_HOME = "alarm_arm_home" +SERVICE_ALARM_ARM_AWAY = "alarm_arm_away" + # #### API / REMOTE #### SERVER_PORT = 8123