2019-02-13 20:21:14 +00:00
|
|
|
"""Support for Abode Home Security system."""
|
2017-08-20 14:55:48 +00:00
|
|
|
import logging
|
2017-09-18 15:39:41 +00:00
|
|
|
from functools import partial
|
2018-02-11 17:20:28 +00:00
|
|
|
from requests.exceptions import HTTPError, ConnectTimeout
|
2017-08-20 14:55:48 +00:00
|
|
|
|
|
|
|
import voluptuous as vol
|
2017-10-18 16:41:14 +00:00
|
|
|
|
|
|
|
from homeassistant.const import (
|
|
|
|
ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME, ATTR_ENTITY_ID, CONF_USERNAME,
|
|
|
|
CONF_PASSWORD, CONF_EXCLUDE, CONF_NAME, CONF_LIGHTS,
|
|
|
|
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
|
2017-08-20 14:55:48 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv
|
2017-10-18 16:41:14 +00:00
|
|
|
from homeassistant.helpers import discovery
|
2017-08-29 15:34:19 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2017-08-20 14:55:48 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-02-14 21:09:22 +00:00
|
|
|
ATTRIBUTION = "Data provided by goabode.com"
|
|
|
|
|
2017-10-18 16:41:14 +00:00
|
|
|
CONF_POLLING = 'polling'
|
2017-08-20 14:55:48 +00:00
|
|
|
|
|
|
|
DOMAIN = 'abode'
|
2018-04-12 20:27:23 +00:00
|
|
|
DEFAULT_CACHEDB = './abodepy_cache.pickle'
|
2017-08-20 14:55:48 +00:00
|
|
|
|
|
|
|
NOTIFICATION_ID = 'abode_notification'
|
|
|
|
NOTIFICATION_TITLE = 'Abode Security Setup'
|
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
EVENT_ABODE_ALARM = 'abode_alarm'
|
|
|
|
EVENT_ABODE_ALARM_END = 'abode_alarm_end'
|
|
|
|
EVENT_ABODE_AUTOMATION = 'abode_automation'
|
|
|
|
EVENT_ABODE_FAULT = 'abode_panel_fault'
|
|
|
|
EVENT_ABODE_RESTORE = 'abode_panel_restore'
|
|
|
|
|
|
|
|
SERVICE_SETTINGS = 'change_setting'
|
|
|
|
SERVICE_CAPTURE_IMAGE = 'capture_image'
|
|
|
|
SERVICE_TRIGGER = 'trigger_quick_action'
|
|
|
|
|
|
|
|
ATTR_DEVICE_ID = 'device_id'
|
|
|
|
ATTR_DEVICE_NAME = 'device_name'
|
|
|
|
ATTR_DEVICE_TYPE = 'device_type'
|
|
|
|
ATTR_EVENT_CODE = 'event_code'
|
|
|
|
ATTR_EVENT_NAME = 'event_name'
|
|
|
|
ATTR_EVENT_TYPE = 'event_type'
|
|
|
|
ATTR_EVENT_UTC = 'event_utc'
|
|
|
|
ATTR_SETTING = 'setting'
|
|
|
|
ATTR_USER_NAME = 'user_name'
|
|
|
|
ATTR_VALUE = 'value'
|
|
|
|
|
|
|
|
ABODE_DEVICE_ID_LIST_SCHEMA = vol.Schema([str])
|
|
|
|
|
2017-08-20 14:55:48 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
|
|
DOMAIN: vol.Schema({
|
|
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
2017-09-18 15:39:41 +00:00
|
|
|
vol.Optional(CONF_NAME): cv.string,
|
|
|
|
vol.Optional(CONF_POLLING, default=False): cv.boolean,
|
|
|
|
vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA,
|
|
|
|
vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA
|
2017-08-20 14:55:48 +00:00
|
|
|
}),
|
|
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
CHANGE_SETTING_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(ATTR_SETTING): cv.string,
|
|
|
|
vol.Required(ATTR_VALUE): cv.string
|
|
|
|
})
|
|
|
|
|
|
|
|
CAPTURE_IMAGE_SCHEMA = vol.Schema({
|
|
|
|
ATTR_ENTITY_ID: cv.entity_ids,
|
|
|
|
})
|
|
|
|
|
|
|
|
TRIGGER_SCHEMA = vol.Schema({
|
|
|
|
ATTR_ENTITY_ID: cv.entity_ids,
|
|
|
|
})
|
|
|
|
|
2017-08-29 15:34:19 +00:00
|
|
|
ABODE_PLATFORMS = [
|
2017-09-18 15:39:41 +00:00
|
|
|
'alarm_control_panel', 'binary_sensor', 'lock', 'switch', 'cover',
|
2018-05-06 17:18:26 +00:00
|
|
|
'camera', 'light', 'sensor'
|
2017-08-29 15:34:19 +00:00
|
|
|
]
|
|
|
|
|
2017-08-20 14:55:48 +00:00
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class AbodeSystem:
|
2017-09-18 15:39:41 +00:00
|
|
|
"""Abode System class."""
|
|
|
|
|
2018-04-12 20:27:23 +00:00
|
|
|
def __init__(self, username, password, cache,
|
|
|
|
name, polling, exclude, lights):
|
2017-09-18 15:39:41 +00:00
|
|
|
"""Initialize the system."""
|
|
|
|
import abodepy
|
2017-10-18 16:41:14 +00:00
|
|
|
self.abode = abodepy.Abode(
|
|
|
|
username, password, auto_login=True, get_devices=True,
|
2018-04-12 20:27:23 +00:00
|
|
|
get_automations=True, cache_path=cache)
|
2017-09-18 15:39:41 +00:00
|
|
|
self.name = name
|
|
|
|
self.polling = polling
|
|
|
|
self.exclude = exclude
|
|
|
|
self.lights = lights
|
|
|
|
self.devices = []
|
|
|
|
|
|
|
|
def is_excluded(self, device):
|
|
|
|
"""Check if a device is configured to be excluded."""
|
|
|
|
return device.device_id in self.exclude
|
|
|
|
|
|
|
|
def is_automation_excluded(self, automation):
|
|
|
|
"""Check if an automation is configured to be excluded."""
|
|
|
|
return automation.automation_id in self.exclude
|
|
|
|
|
|
|
|
def is_light(self, device):
|
|
|
|
"""Check if a switch device is configured as a light."""
|
|
|
|
import abodepy.helpers.constants as CONST
|
|
|
|
|
|
|
|
return (device.generic_type == CONST.TYPE_LIGHT or
|
|
|
|
(device.generic_type == CONST.TYPE_SWITCH and
|
|
|
|
device.device_id in self.lights))
|
|
|
|
|
|
|
|
|
2017-08-20 14:55:48 +00:00
|
|
|
def setup(hass, config):
|
|
|
|
"""Set up Abode component."""
|
2017-09-18 15:39:41 +00:00
|
|
|
from abodepy.exceptions import AbodeException
|
2017-08-29 15:34:19 +00:00
|
|
|
|
2017-08-20 14:55:48 +00:00
|
|
|
conf = config[DOMAIN]
|
|
|
|
username = conf.get(CONF_USERNAME)
|
|
|
|
password = conf.get(CONF_PASSWORD)
|
2017-09-18 15:39:41 +00:00
|
|
|
name = conf.get(CONF_NAME)
|
|
|
|
polling = conf.get(CONF_POLLING)
|
|
|
|
exclude = conf.get(CONF_EXCLUDE)
|
|
|
|
lights = conf.get(CONF_LIGHTS)
|
2017-08-20 14:55:48 +00:00
|
|
|
|
|
|
|
try:
|
2018-04-12 20:27:23 +00:00
|
|
|
cache = hass.config.path(DEFAULT_CACHEDB)
|
2017-09-18 15:39:41 +00:00
|
|
|
hass.data[DOMAIN] = AbodeSystem(
|
2018-04-12 20:27:23 +00:00
|
|
|
username, password, cache, name, polling, exclude, lights)
|
2017-09-18 15:39:41 +00:00
|
|
|
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
2017-08-20 14:55:48 +00:00
|
|
|
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
2017-09-18 15:39:41 +00:00
|
|
|
|
2017-08-20 14:55:48 +00:00
|
|
|
hass.components.persistent_notification.create(
|
|
|
|
'Error: {}<br />'
|
|
|
|
'You will need to restart hass after fixing.'
|
|
|
|
''.format(ex),
|
|
|
|
title=NOTIFICATION_TITLE,
|
|
|
|
notification_id=NOTIFICATION_ID)
|
|
|
|
return False
|
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
setup_hass_services(hass)
|
|
|
|
setup_hass_events(hass)
|
|
|
|
setup_abode_events(hass)
|
|
|
|
|
2017-08-29 15:34:19 +00:00
|
|
|
for platform in ABODE_PLATFORMS:
|
|
|
|
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def setup_hass_services(hass):
|
|
|
|
"""Home assistant services."""
|
|
|
|
from abodepy.exceptions import AbodeException
|
|
|
|
|
|
|
|
def change_setting(call):
|
|
|
|
"""Change an Abode system setting."""
|
|
|
|
setting = call.data.get(ATTR_SETTING)
|
|
|
|
value = call.data.get(ATTR_VALUE)
|
|
|
|
|
|
|
|
try:
|
|
|
|
hass.data[DOMAIN].abode.set_setting(setting, value)
|
|
|
|
except AbodeException as ex:
|
|
|
|
_LOGGER.warning(ex)
|
|
|
|
|
|
|
|
def capture_image(call):
|
|
|
|
"""Capture a new image."""
|
|
|
|
entity_ids = call.data.get(ATTR_ENTITY_ID)
|
|
|
|
|
|
|
|
target_devices = [device for device in hass.data[DOMAIN].devices
|
|
|
|
if device.entity_id in entity_ids]
|
|
|
|
|
|
|
|
for device in target_devices:
|
|
|
|
device.capture()
|
|
|
|
|
|
|
|
def trigger_quick_action(call):
|
|
|
|
"""Trigger a quick action."""
|
|
|
|
entity_ids = call.data.get(ATTR_ENTITY_ID, None)
|
|
|
|
|
|
|
|
target_devices = [device for device in hass.data[DOMAIN].devices
|
|
|
|
if device.entity_id in entity_ids]
|
|
|
|
|
|
|
|
for device in target_devices:
|
|
|
|
device.trigger()
|
|
|
|
|
|
|
|
hass.services.register(
|
|
|
|
DOMAIN, SERVICE_SETTINGS, change_setting,
|
|
|
|
schema=CHANGE_SETTING_SCHEMA)
|
|
|
|
|
|
|
|
hass.services.register(
|
|
|
|
DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image,
|
|
|
|
schema=CAPTURE_IMAGE_SCHEMA)
|
|
|
|
|
|
|
|
hass.services.register(
|
|
|
|
DOMAIN, SERVICE_TRIGGER, trigger_quick_action,
|
|
|
|
schema=TRIGGER_SCHEMA)
|
|
|
|
|
|
|
|
|
|
|
|
def setup_hass_events(hass):
|
2017-10-18 16:41:14 +00:00
|
|
|
"""Home Assistant start and stop callbacks."""
|
2017-09-18 15:39:41 +00:00
|
|
|
def startup(event):
|
|
|
|
"""Listen for push events."""
|
|
|
|
hass.data[DOMAIN].abode.events.start()
|
|
|
|
|
2017-08-29 15:34:19 +00:00
|
|
|
def logout(event):
|
|
|
|
"""Logout of Abode."""
|
2017-09-18 15:39:41 +00:00
|
|
|
if not hass.data[DOMAIN].polling:
|
|
|
|
hass.data[DOMAIN].abode.events.stop()
|
|
|
|
|
|
|
|
hass.data[DOMAIN].abode.logout()
|
2017-08-29 15:34:19 +00:00
|
|
|
_LOGGER.info("Logged out of Abode")
|
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
if not hass.data[DOMAIN].polling:
|
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, startup)
|
|
|
|
|
2017-08-29 15:34:19 +00:00
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, logout)
|
|
|
|
|
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
def setup_abode_events(hass):
|
|
|
|
"""Event callbacks."""
|
|
|
|
import abodepy.helpers.timeline as TIMELINE
|
2017-08-29 15:34:19 +00:00
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
def event_callback(event, event_json):
|
|
|
|
"""Handle an event callback from Abode."""
|
|
|
|
data = {
|
|
|
|
ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ''),
|
|
|
|
ATTR_DEVICE_NAME: event_json.get(ATTR_DEVICE_NAME, ''),
|
|
|
|
ATTR_DEVICE_TYPE: event_json.get(ATTR_DEVICE_TYPE, ''),
|
|
|
|
ATTR_EVENT_CODE: event_json.get(ATTR_EVENT_CODE, ''),
|
|
|
|
ATTR_EVENT_NAME: event_json.get(ATTR_EVENT_NAME, ''),
|
|
|
|
ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ''),
|
|
|
|
ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ''),
|
|
|
|
ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ''),
|
|
|
|
ATTR_DATE: event_json.get(ATTR_DATE, ''),
|
|
|
|
ATTR_TIME: event_json.get(ATTR_TIME, ''),
|
|
|
|
}
|
|
|
|
|
|
|
|
hass.bus.fire(event, data)
|
|
|
|
|
|
|
|
events = [TIMELINE.ALARM_GROUP, TIMELINE.ALARM_END_GROUP,
|
|
|
|
TIMELINE.PANEL_FAULT_GROUP, TIMELINE.PANEL_RESTORE_GROUP,
|
|
|
|
TIMELINE.AUTOMATION_GROUP]
|
|
|
|
|
|
|
|
for event in events:
|
|
|
|
hass.data[DOMAIN].abode.events.add_event_callback(
|
|
|
|
event,
|
|
|
|
partial(event_callback, event))
|
2017-08-20 14:55:48 +00:00
|
|
|
|
|
|
|
|
2017-08-29 15:34:19 +00:00
|
|
|
class AbodeDevice(Entity):
|
|
|
|
"""Representation of an Abode device."""
|
2017-08-20 14:55:48 +00:00
|
|
|
|
2017-09-18 15:39:41 +00:00
|
|
|
def __init__(self, data, device):
|
2017-08-29 15:34:19 +00:00
|
|
|
"""Initialize a sensor for Abode device."""
|
2017-09-18 15:39:41 +00:00
|
|
|
self._data = data
|
2017-08-29 15:34:19 +00:00
|
|
|
self._device = device
|
2017-08-20 14:55:48 +00:00
|
|
|
|
2018-10-01 06:52:42 +00:00
|
|
|
async def async_added_to_hass(self):
|
2017-08-29 15:34:19 +00:00
|
|
|
"""Subscribe Abode events."""
|
|
|
|
self.hass.async_add_job(
|
2017-09-18 15:39:41 +00:00
|
|
|
self._data.abode.events.add_device_callback,
|
|
|
|
self._device.device_id, self._update_callback
|
2017-08-29 15:34:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""Return the polling state."""
|
2017-09-18 15:39:41 +00:00
|
|
|
return self._data.polling
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
"""Update automation state."""
|
|
|
|
self._device.refresh()
|
2017-08-20 14:55:48 +00:00
|
|
|
|
2017-08-29 15:34:19 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the sensor."""
|
|
|
|
return self._device.name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
|
|
|
"""Return the state attributes."""
|
|
|
|
return {
|
2019-02-14 21:09:22 +00:00
|
|
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
2017-08-29 15:34:19 +00:00
|
|
|
'device_id': self._device.device_id,
|
|
|
|
'battery_low': self._device.battery_low,
|
2017-09-20 18:51:09 +00:00
|
|
|
'no_response': self._device.no_response,
|
|
|
|
'device_type': self._device.type
|
2017-08-29 15:34:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def _update_callback(self, device):
|
|
|
|
"""Update the device state."""
|
|
|
|
self.schedule_update_ha_state()
|
2017-09-18 15:39:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AbodeAutomation(Entity):
|
|
|
|
"""Representation of an Abode automation."""
|
|
|
|
|
|
|
|
def __init__(self, data, automation, event=None):
|
|
|
|
"""Initialize for Abode automation."""
|
|
|
|
self._data = data
|
|
|
|
self._automation = automation
|
|
|
|
self._event = event
|
|
|
|
|
2018-10-01 06:52:42 +00:00
|
|
|
async def async_added_to_hass(self):
|
2017-09-18 15:39:41 +00:00
|
|
|
"""Subscribe Abode events."""
|
|
|
|
if self._event:
|
|
|
|
self.hass.async_add_job(
|
|
|
|
self._data.abode.events.add_event_callback,
|
|
|
|
self._event, self._update_callback
|
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""Return the polling state."""
|
|
|
|
return self._data.polling
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
"""Update automation state."""
|
|
|
|
self._automation.refresh()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the sensor."""
|
|
|
|
return self._automation.name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
|
|
|
"""Return the state attributes."""
|
|
|
|
return {
|
2019-02-14 21:09:22 +00:00
|
|
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
2017-09-18 15:39:41 +00:00
|
|
|
'automation_id': self._automation.automation_id,
|
|
|
|
'type': self._automation.type,
|
|
|
|
'sub_type': self._automation.sub_type
|
|
|
|
}
|
|
|
|
|
|
|
|
def _update_callback(self, device):
|
|
|
|
"""Update the device state."""
|
|
|
|
self._automation.refresh()
|
|
|
|
self.schedule_update_ha_state()
|