Add abode support for CUE automations (#32296)

* Add support for CUE automations

* Update requirements

* Minor update to string name
This commit is contained in:
Paulus Schoutsen 2020-03-04 14:54:28 -08:00 committed by GitHub
parent 2abdfc9da6
commit d1beb92c5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 66 additions and 102 deletions

View File

@ -2,7 +2,6 @@
from asyncio import gather from asyncio import gather
from copy import deepcopy from copy import deepcopy
from functools import partial from functools import partial
import logging
from abodepy import Abode from abodepy import Abode
from abodepy.exceptions import AbodeException from abodepy.exceptions import AbodeException
@ -24,15 +23,13 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER
_LOGGER = logging.getLogger(__name__)
CONF_POLLING = "polling" CONF_POLLING = "polling"
SERVICE_SETTINGS = "change_setting" SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image" SERVICE_CAPTURE_IMAGE = "capture_image"
SERVICE_TRIGGER = "trigger_quick_action" SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
ATTR_DEVICE_ID = "device_id" ATTR_DEVICE_ID = "device_id"
ATTR_DEVICE_NAME = "device_name" ATTR_DEVICE_NAME = "device_name"
@ -47,8 +44,6 @@ ATTR_APP_TYPE = "app_type"
ATTR_EVENT_BY = "event_by" ATTR_EVENT_BY = "event_by"
ATTR_VALUE = "value" ATTR_VALUE = "value"
ABODE_DEVICE_ID_LIST_SCHEMA = vol.Schema([str])
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
@ -68,7 +63,7 @@ CHANGE_SETTING_SCHEMA = vol.Schema(
CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
TRIGGER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
ABODE_PLATFORMS = [ ABODE_PLATFORMS = [
"alarm_control_panel", "alarm_control_panel",
@ -87,7 +82,6 @@ class AbodeSystem:
def __init__(self, abode, polling): def __init__(self, abode, polling):
"""Initialize the system.""" """Initialize the system."""
self.abode = abode self.abode = abode
self.polling = polling self.polling = polling
self.entity_ids = set() self.entity_ids = set()
@ -124,7 +118,7 @@ async def async_setup_entry(hass, config_entry):
hass.data[DOMAIN] = AbodeSystem(abode, polling) hass.data[DOMAIN] = AbodeSystem(abode, polling)
except (AbodeException, ConnectTimeout, HTTPError) as ex: except (AbodeException, ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Abode: %s", str(ex)) LOGGER.error("Unable to connect to Abode: %s", str(ex))
return False return False
for platform in ABODE_PLATFORMS: for platform in ABODE_PLATFORMS:
@ -143,7 +137,7 @@ async def async_unload_entry(hass, config_entry):
"""Unload a config entry.""" """Unload a config entry."""
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS) hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE) hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER) hass.services.async_remove(DOMAIN, SERVICE_TRIGGER_AUTOMATION)
tasks = [] tasks = []
@ -174,7 +168,7 @@ def setup_hass_services(hass):
try: try:
hass.data[DOMAIN].abode.set_setting(setting, value) hass.data[DOMAIN].abode.set_setting(setting, value)
except AbodeException as ex: except AbodeException as ex:
_LOGGER.warning(ex) LOGGER.warning(ex)
def capture_image(call): def capture_image(call):
"""Capture a new image.""" """Capture a new image."""
@ -190,8 +184,8 @@ def setup_hass_services(hass):
signal = f"abode_camera_capture_{entity_id}" signal = f"abode_camera_capture_{entity_id}"
dispatcher_send(hass, signal) dispatcher_send(hass, signal)
def trigger_quick_action(call): def trigger_automation(call):
"""Trigger a quick action.""" """Trigger an Abode automation."""
entity_ids = call.data.get(ATTR_ENTITY_ID, None) entity_ids = call.data.get(ATTR_ENTITY_ID, None)
target_entities = [ target_entities = [
@ -201,7 +195,7 @@ def setup_hass_services(hass):
] ]
for entity_id in target_entities: for entity_id in target_entities:
signal = f"abode_trigger_quick_action_{entity_id}" signal = f"abode_trigger_automation_{entity_id}"
dispatcher_send(hass, signal) dispatcher_send(hass, signal)
hass.services.register( hass.services.register(
@ -213,7 +207,7 @@ def setup_hass_services(hass):
) )
hass.services.register( hass.services.register(
DOMAIN, SERVICE_TRIGGER, trigger_quick_action, schema=TRIGGER_SCHEMA DOMAIN, SERVICE_TRIGGER_AUTOMATION, trigger_automation, schema=AUTOMATION_SCHEMA
) )
@ -226,7 +220,7 @@ async def setup_hass_events(hass):
hass.data[DOMAIN].abode.events.stop() hass.data[DOMAIN].abode.events.stop()
hass.data[DOMAIN].abode.logout() hass.data[DOMAIN].abode.logout()
_LOGGER.info("Logged out of Abode") LOGGER.info("Logged out of Abode")
if not hass.data[DOMAIN].polling: if not hass.data[DOMAIN].polling:
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start) await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
@ -384,11 +378,14 @@ class AbodeAutomation(Entity):
"""Return the state attributes.""" """Return the state attributes."""
return { return {
ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_ATTRIBUTION: ATTRIBUTION,
"automation_id": self._automation.automation_id, "type": "CUE automation",
"type": self._automation.type,
"sub_type": self._automation.sub_type,
} }
@property
def unique_id(self):
"""Return a unique ID to use for this automation."""
return self._automation.automation_id
def _update_callback(self, device): def _update_callback(self, device):
"""Update the automation state.""" """Update the automation state."""
self._automation.refresh() self._automation.refresh()

View File

@ -1,6 +1,4 @@
"""Support for Abode Security System alarm control panels.""" """Support for Abode Security System alarm control panels."""
import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel.const import ( from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_AWAY,
@ -16,8 +14,6 @@ from homeassistant.const import (
from . import AbodeDevice from . import AbodeDevice
from .const import ATTRIBUTION, DOMAIN from .const import ATTRIBUTION, DOMAIN
_LOGGER = logging.getLogger(__name__)
ICON = "mdi:security" ICON = "mdi:security"
@ -50,6 +46,11 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
state = None state = None
return state return state
@property
def code_arm_required(self):
"""Whether the code is required for arm actions."""
return False
@property @property
def supported_features(self) -> int: def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""

View File

@ -1,17 +1,11 @@
"""Support for Abode Security System binary sensors.""" """Support for Abode Security System binary sensors."""
import logging
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
import abodepy.helpers.timeline as TIMELINE
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import AbodeAutomation, AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode binary sensor devices.""" """Set up Abode binary sensor devices."""
@ -30,13 +24,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for device in data.abode.get_devices(generic_type=device_types): for device in data.abode.get_devices(generic_type=device_types):
entities.append(AbodeBinarySensor(data, device)) entities.append(AbodeBinarySensor(data, device))
for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION):
entities.append(
AbodeQuickActionBinarySensor(
data, automation, TIMELINE.AUTOMATION_EDIT_GROUP
)
)
async_add_entities(entities) async_add_entities(entities)
@ -52,22 +39,3 @@ class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
def device_class(self): def device_class(self):
"""Return the class of the binary sensor.""" """Return the class of the binary sensor."""
return self._device.generic_type return self._device.generic_type
class AbodeQuickActionBinarySensor(AbodeAutomation, BinarySensorDevice):
"""A binary sensor implementation for Abode quick action automations."""
async def async_added_to_hass(self):
"""Subscribe Abode events."""
await super().async_added_to_hass()
signal = f"abode_trigger_quick_action_{self.entity_id}"
async_dispatcher_connect(self.hass, signal, self.trigger)
def trigger(self):
"""Trigger a quick automation."""
self._automation.trigger()
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._automation.is_active

View File

@ -1,6 +1,5 @@
"""Support for Abode Security System cameras.""" """Support for Abode Security System cameras."""
from datetime import timedelta from datetime import timedelta
import logging
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
import abodepy.helpers.timeline as TIMELINE import abodepy.helpers.timeline as TIMELINE
@ -11,12 +10,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import Throttle from homeassistant.util import Throttle
from . import AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN, LOGGER
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode camera devices.""" """Set up Abode camera devices."""
@ -71,7 +68,7 @@ class AbodeCamera(AbodeDevice, Camera):
self._response.raise_for_status() self._response.raise_for_status()
except requests.HTTPError as err: except requests.HTTPError as err:
_LOGGER.warning("Failed to get camera image: %s", err) LOGGER.warning("Failed to get camera image: %s", err)
self._response = None self._response = None
else: else:
self._response = None self._response = None

View File

@ -1,6 +1,4 @@
"""Config flow for the Abode Security System component.""" """Config flow for the Abode Security System component."""
import logging
from abodepy import Abode from abodepy import Abode
from abodepy.exceptions import AbodeException from abodepy.exceptions import AbodeException
from requests.exceptions import ConnectTimeout, HTTPError from requests.exceptions import ConnectTimeout, HTTPError
@ -10,12 +8,10 @@ from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import callback
from .const import DEFAULT_CACHEDB, DOMAIN # pylint: disable=unused-import from .const import DEFAULT_CACHEDB, DOMAIN, LOGGER # pylint: disable=unused-import
CONF_POLLING = "polling" CONF_POLLING = "polling"
_LOGGER = logging.getLogger(__name__)
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Abode.""" """Config flow for Abode."""
@ -32,7 +28,6 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
if self._async_current_entries(): if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed") return self.async_abort(reason="single_instance_allowed")
@ -50,7 +45,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
) )
except (AbodeException, ConnectTimeout, HTTPError) as ex: except (AbodeException, ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Abode: %s", str(ex)) LOGGER.error("Unable to connect to Abode: %s", str(ex))
if ex.errcode == 400: if ex.errcode == 400:
return self._show_form({"base": "invalid_credentials"}) return self._show_form({"base": "invalid_credentials"})
return self._show_form({"base": "connection_error"}) return self._show_form({"base": "connection_error"})
@ -76,7 +71,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, import_config): async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml.""" """Import a config entry from configuration.yaml."""
if self._async_current_entries(): if self._async_current_entries():
_LOGGER.warning("Only one configuration of abode is allowed.") LOGGER.warning("Only one configuration of abode is allowed.")
return self.async_abort(reason="single_instance_allowed") return self.async_abort(reason="single_instance_allowed")
return await self.async_step_user(import_config) return await self.async_step_user(import_config)

View File

@ -1,4 +1,8 @@
"""Constants for the Abode Security System component.""" """Constants for the Abode Security System component."""
import logging
LOGGER = logging.getLogger(__package__)
DOMAIN = "abode" DOMAIN = "abode"
ATTRIBUTION = "Data provided by goabode.com" ATTRIBUTION = "Data provided by goabode.com"

View File

@ -1,6 +1,4 @@
"""Support for Abode Security System covers.""" """Support for Abode Security System covers."""
import logging
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice
@ -8,8 +6,6 @@ from homeassistant.components.cover import CoverDevice
from . import AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode cover devices.""" """Set up Abode cover devices."""

View File

@ -1,5 +1,4 @@
"""Support for Abode Security System lights.""" """Support for Abode Security System lights."""
import logging
from math import ceil from math import ceil
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
@ -21,8 +20,6 @@ from homeassistant.util.color import (
from . import AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode light devices.""" """Set up Abode light devices."""

View File

@ -1,6 +1,4 @@
"""Support for the Abode Security System locks.""" """Support for the Abode Security System locks."""
import logging
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.lock import LockDevice from homeassistant.components.lock import LockDevice
@ -8,8 +6,6 @@ from homeassistant.components.lock import LockDevice
from . import AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode lock devices.""" """Set up Abode lock devices."""

View File

@ -3,7 +3,7 @@
"name": "Abode", "name": "Abode",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/abode", "documentation": "https://www.home-assistant.io/integrations/abode",
"requirements": ["abodepy==0.17.0"], "requirements": ["abodepy==0.18.1"],
"dependencies": [], "dependencies": [],
"codeowners": ["@shred86"] "codeowners": ["@shred86"]
} }

View File

@ -1,6 +1,4 @@
"""Support for Abode Security System sensors.""" """Support for Abode Security System sensors."""
import logging
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.const import ( from homeassistant.const import (
@ -12,8 +10,6 @@ from homeassistant.const import (
from . import AbodeDevice from . import AbodeDevice
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# Sensor types: Name, icon # Sensor types: Name, icon
SENSOR_TYPES = { SENSOR_TYPES = {
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE], CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE],

View File

@ -7,7 +7,7 @@ change_setting:
fields: fields:
setting: {description: Setting to change., example: beeper_mute} setting: {description: Setting to change., example: beeper_mute}
value: {description: Value of the setting., example: '1'} value: {description: Value of the setting., example: '1'}
trigger_quick_action: trigger_automation:
description: Trigger an Abode quick action. description: Trigger an Abode automation.
fields: fields:
entity_id: {description: Entity id of the quick action to trigger., example: binary_sensor.home_quick_action} entity_id: {description: Entity id of the automation to trigger., example: switch.my_automation}

View File

@ -1,18 +1,17 @@
"""Support for Abode Security System switches.""" """Support for Abode Security System switches."""
import logging
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
import abodepy.helpers.timeline as TIMELINE import abodepy.helpers.timeline as TIMELINE
from homeassistant.components.switch import SwitchDevice from homeassistant.components.switch import SwitchDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import AbodeAutomation, AbodeDevice from . import AbodeAutomation, AbodeDevice
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE] DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
ICON = "mdi:robot"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode switch devices.""" """Set up Abode switch devices."""
@ -24,7 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for device in data.abode.get_devices(generic_type=device_type): for device in data.abode.get_devices(generic_type=device_type):
entities.append(AbodeSwitch(data, device)) entities.append(AbodeSwitch(data, device))
for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION): for automation in data.abode.get_automations():
entities.append( entities.append(
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP) AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
) )
@ -52,15 +51,33 @@ class AbodeSwitch(AbodeDevice, SwitchDevice):
class AbodeAutomationSwitch(AbodeAutomation, SwitchDevice): class AbodeAutomationSwitch(AbodeAutomation, SwitchDevice):
"""A switch implementation for Abode automations.""" """A switch implementation for Abode automations."""
async def async_added_to_hass(self):
"""Subscribe Abode events."""
await super().async_added_to_hass()
signal = f"abode_trigger_automation_{self.entity_id}"
async_dispatcher_connect(self.hass, signal, self.trigger)
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn on the device.""" """Enable the automation."""
self._automation.set_active(True) if self._automation.enable(True):
self.schedule_update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn off the device.""" """Disable the automation."""
self._automation.set_active(False) if self._automation.enable(False):
self.schedule_update_ha_state()
def trigger(self):
"""Trigger the automation."""
self._automation.trigger()
@property @property
def is_on(self): def is_on(self):
"""Return True if the binary sensor is on.""" """Return True if the automation is enabled."""
return self._automation.is_active return self._automation.is_enabled
@property
def icon(self):
"""Return the robot icon to match Home Assistant automations."""
return ICON

View File

@ -106,7 +106,7 @@ WazeRouteCalculator==0.12
YesssSMS==0.4.1 YesssSMS==0.4.1
# homeassistant.components.abode # homeassistant.components.abode
abodepy==0.17.0 abodepy==0.18.1
# homeassistant.components.mcp23017 # homeassistant.components.mcp23017
adafruit-blinka==3.9.0 adafruit-blinka==3.9.0

View File

@ -26,7 +26,7 @@ RtmAPI==0.7.2
YesssSMS==0.4.1 YesssSMS==0.4.1
# homeassistant.components.abode # homeassistant.components.abode
abodepy==0.17.0 abodepy==0.18.1
# homeassistant.components.androidtv # homeassistant.components.androidtv
adb-shell==0.1.1 adb-shell==0.1.1