1
mirror of https://github.com/home-assistant/core synced 2024-09-15 17:29:45 +02:00
ha-core/homeassistant/components/abode/__init__.py

350 lines
11 KiB
Python
Raw Normal View History

"""Support for the Abode Security System."""
2022-01-10 15:54:09 +01:00
from __future__ import annotations
from functools import partial
2022-01-10 15:54:09 +01:00
from abodepy import Abode, AbodeAutomation as AbodeAuto
from abodepy.devices import AbodeDevice as AbodeDev
2020-11-27 13:39:26 +01:00
from abodepy.exceptions import AbodeAuthenticationException, AbodeException
2019-10-12 22:02:12 +02:00
import abodepy.helpers.timeline as TIMELINE
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
2017-10-18 18:41:14 +02:00
from homeassistant.config_entries import ConfigEntry
2017-10-18 18:41:14 +02:00
from homeassistant.const import (
2019-07-31 21:25:30 +02:00
ATTR_DATE,
ATTR_DEVICE_ID,
2019-07-31 21:25:30 +02:00
ATTR_ENTITY_ID,
ATTR_TIME,
2019-07-31 21:25:30 +02:00
CONF_PASSWORD,
CONF_USERNAME,
2019-07-31 21:25:30 +02:00
EVENT_HOMEASSISTANT_STOP,
Platform,
2019-07-31 21:25:30 +02:00
)
2022-01-10 15:54:09 +01:00
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
2022-01-10 15:54:09 +01:00
from homeassistant.helpers import config_validation as cv, entity
from homeassistant.helpers.dispatcher import dispatcher_send
2022-01-10 15:54:09 +01:00
from .const import ATTRIBUTION, CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
2019-07-31 21:25:30 +02:00
SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image"
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
2019-07-31 21:25:30 +02:00
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_APP_TYPE = "app_type"
ATTR_EVENT_BY = "event_by"
2019-07-31 21:25:30 +02:00
ATTR_VALUE = "value"
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
2019-07-31 21:25:30 +02: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})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.LOCK,
Platform.SWITCH,
Platform.COVER,
Platform.CAMERA,
Platform.LIGHT,
Platform.SENSOR,
]
class AbodeSystem:
"""Abode System class."""
2022-01-10 15:54:09 +01:00
def __init__(self, abode: Abode, polling: bool) -> None:
"""Initialize the system."""
self.abode = abode
self.polling = polling
2022-01-10 15:54:09 +01:00
self.entity_ids: set[str | None] = set()
self.logout_listener = None
2022-01-10 15:54:09 +01:00
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Abode integration from a config entry."""
2022-01-10 15:54:09 +01:00
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
polling = entry.data[CONF_POLLING]
2020-11-27 13:39:26 +01:00
cache = hass.config.path(DEFAULT_CACHEDB)
# For previous config entries where unique_id is None
2022-01-10 15:54:09 +01:00
if entry.unique_id is None:
2020-11-27 13:39:26 +01:00
hass.config_entries.async_update_entry(
2022-01-10 15:54:09 +01:00
entry, unique_id=entry.data[CONF_USERNAME]
2020-11-27 13:39:26 +01:00
)
try:
abode = await hass.async_add_executor_job(
Abode, username, password, True, True, True, cache
2019-07-31 21:25:30 +02:00
)
2020-11-27 13:39:26 +01:00
except AbodeAuthenticationException as ex:
raise ConfigEntryAuthFailed(f"Invalid credentials: {ex}") from ex
except (AbodeException, ConnectTimeout, HTTPError) as ex:
raise ConfigEntryNotReady(f"Unable to connect to Abode: {ex}") from ex
2020-11-27 13:39:26 +01:00
hass.data[DOMAIN] = AbodeSystem(abode, polling)
2022-01-10 15:54:09 +01:00
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await setup_hass_events(hass)
await hass.async_add_executor_job(setup_hass_services, hass)
await hass.async_add_executor_job(setup_abode_events, hass)
return True
2022-01-10 15:54:09 +01:00
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER_AUTOMATION)
2022-01-10 15:54:09 +01:00
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout)
hass.data[DOMAIN].logout_listener()
hass.data.pop(DOMAIN)
return unload_ok
def setup_hass_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
def change_setting(call: ServiceCall) -> None:
"""Change an Abode system setting."""
setting = call.data[ATTR_SETTING]
value = call.data[ATTR_VALUE]
try:
hass.data[DOMAIN].abode.set_setting(setting, value)
except AbodeException as ex:
LOGGER.warning(ex)
def capture_image(call: ServiceCall) -> None:
"""Capture a new image."""
entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [
entity_id
for entity_id in hass.data[DOMAIN].entity_ids
if entity_id in entity_ids
2019-07-31 21:25:30 +02:00
]
for entity_id in target_entities:
signal = f"abode_camera_capture_{entity_id}"
dispatcher_send(hass, signal)
def trigger_automation(call: ServiceCall) -> None:
"""Trigger an Abode automation."""
entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [
entity_id
for entity_id in hass.data[DOMAIN].entity_ids
if entity_id in entity_ids
2019-07-31 21:25:30 +02:00
]
for entity_id in target_entities:
signal = f"abode_trigger_automation_{entity_id}"
dispatcher_send(hass, signal)
hass.services.register(
2019-07-31 21:25:30 +02:00
DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA
)
hass.services.register(
2019-07-31 21:25:30 +02:00
DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, schema=CAPTURE_IMAGE_SCHEMA
)
hass.services.register(
DOMAIN, SERVICE_TRIGGER_AUTOMATION, trigger_automation, schema=AUTOMATION_SCHEMA
2019-07-31 21:25:30 +02:00
)
async def setup_hass_events(hass: HomeAssistant) -> None:
2017-10-18 18:41:14 +02:00
"""Home Assistant start and stop callbacks."""
2019-07-31 21:25:30 +02:00
2022-01-10 15:54:09 +01:00
def logout(event: Event) -> None:
"""Logout of Abode."""
if not hass.data[DOMAIN].polling:
hass.data[DOMAIN].abode.events.stop()
hass.data[DOMAIN].abode.logout()
LOGGER.info("Logged out of Abode")
if not hass.data[DOMAIN].polling:
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
hass.data[DOMAIN].logout_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, logout
)
def setup_abode_events(hass: HomeAssistant) -> None:
"""Event callbacks."""
2022-01-10 15:54:09 +01:00
def event_callback(event: str, event_json: dict[str, str]) -> None:
"""Handle an event callback from Abode."""
data = {
2019-07-31 21:25:30 +02:00
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_APP_TYPE: event_json.get(ATTR_APP_TYPE, ""),
ATTR_EVENT_BY: event_json.get(ATTR_EVENT_BY, ""),
2019-07-31 21:25:30 +02:00
ATTR_DATE: event_json.get(ATTR_DATE, ""),
ATTR_TIME: event_json.get(ATTR_TIME, ""),
}
hass.bus.fire(event, data)
2019-07-31 21:25:30 +02:00
events = [
TIMELINE.ALARM_GROUP,
TIMELINE.ALARM_END_GROUP,
TIMELINE.PANEL_FAULT_GROUP,
TIMELINE.PANEL_RESTORE_GROUP,
TIMELINE.AUTOMATION_GROUP,
TIMELINE.DISARM_GROUP,
TIMELINE.ARM_GROUP,
TIMELINE.ARM_FAULT_GROUP,
TIMELINE.TEST_GROUP,
TIMELINE.CAPTURE_GROUP,
TIMELINE.DEVICE_GROUP,
2019-07-31 21:25:30 +02:00
]
for event in events:
hass.data[DOMAIN].abode.events.add_event_callback(
2019-07-31 21:25:30 +02:00
event, partial(event_callback, event)
)
2022-01-10 15:54:09 +01:00
class AbodeEntity(entity.Entity):
"""Representation of an Abode entity."""
_attr_attribution = ATTRIBUTION
2022-01-10 15:54:09 +01:00
def __init__(self, data: AbodeSystem) -> None:
"""Initialize Abode entity."""
self._data = data
self._attr_should_poll = data.polling
2022-01-10 15:54:09 +01:00
async def async_added_to_hass(self) -> None:
"""Subscribe to Abode connection status updates."""
await self.hass.async_add_executor_job(
self._data.abode.events.add_connection_status_callback,
self.unique_id,
self._update_connection_status,
)
self.hass.data[DOMAIN].entity_ids.add(self.entity_id)
2022-01-10 15:54:09 +01:00
async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from Abode connection status updates."""
await self.hass.async_add_executor_job(
self._data.abode.events.remove_connection_status_callback, self.unique_id
)
2022-01-10 15:54:09 +01:00
def _update_connection_status(self) -> None:
"""Update the entity available property."""
self._attr_available = self._data.abode.events.connected
self.schedule_update_ha_state()
class AbodeDevice(AbodeEntity):
"""Representation of an Abode device."""
2022-01-10 15:54:09 +01:00
def __init__(self, data: AbodeSystem, device: AbodeDev) -> None:
"""Initialize Abode device."""
super().__init__(data)
self._device = device
self._attr_name = device.name
self._attr_unique_id = device.device_uuid
2022-01-10 15:54:09 +01:00
async def async_added_to_hass(self) -> None:
"""Subscribe to device events."""
await super().async_added_to_hass()
await self.hass.async_add_executor_job(
self._data.abode.events.add_device_callback,
2019-07-31 21:25:30 +02:00
self._device.device_id,
self._update_callback,
)
2022-01-10 15:54:09 +01:00
async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from device events."""
await super().async_will_remove_from_hass()
await self.hass.async_add_executor_job(
self._data.abode.events.remove_all_device_callbacks, self._device.device_id
)
2022-01-10 15:54:09 +01:00
def update(self) -> None:
"""Update device state."""
self._device.refresh()
@property
2022-01-10 15:54:09 +01:00
def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes."""
return {
2019-07-31 21:25:30 +02:00
"device_id": self._device.device_id,
"battery_low": self._device.battery_low,
"no_response": self._device.no_response,
"device_type": self._device.type,
}
@property
2022-01-10 15:54:09 +01:00
def device_info(self) -> entity.DeviceInfo:
"""Return device registry information for this entity."""
2022-01-10 15:54:09 +01:00
return entity.DeviceInfo(
2021-10-22 16:35:39 +02:00
identifiers={(DOMAIN, self._device.device_id)},
manufacturer="Abode",
model=self._device.type,
name=self._device.name,
)
2022-01-10 15:54:09 +01:00
def _update_callback(self, device: AbodeDev) -> None:
"""Update the device state."""
self.schedule_update_ha_state()
class AbodeAutomation(AbodeEntity):
"""Representation of an Abode automation."""
2022-01-10 15:54:09 +01:00
def __init__(self, data: AbodeSystem, automation: AbodeAuto) -> None:
"""Initialize for Abode automation."""
super().__init__(data)
self._automation = automation
self._attr_name = automation.name
self._attr_unique_id = automation.automation_id
self._attr_extra_state_attributes = {
"type": "CUE automation",
}
2022-01-10 15:54:09 +01:00
def update(self) -> None:
"""Update automation state."""
self._automation.refresh()