core/homeassistant/components/abode/__init__.py

355 lines
11 KiB
Python
Raw Normal View History

"""Support for the Abode Security System."""
2022-01-10 14:54:09 +00:00
from __future__ import annotations
from dataclasses import dataclass, field
from functools import partial
from jaraco.abode.automation import Automation as AbodeAuto
from jaraco.abode.client import Client as Abode
from jaraco.abode.devices.base import Device as AbodeDev
from jaraco.abode.exceptions import (
AuthenticationException as AbodeAuthenticationException,
Exception as AbodeException,
)
from jaraco.abode.helpers.timeline import Groups as GROUPS
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
2017-10-18 16:41:14 +00:00
from homeassistant.config_entries import ConfigEntry
2017-10-18 16:41:14 +00:00
from homeassistant.const import (
2019-07-31 19:25:30 +00:00
ATTR_DATE,
ATTR_DEVICE_ID,
2019-07-31 19:25:30 +00:00
ATTR_ENTITY_ID,
ATTR_TIME,
2019-07-31 19:25:30 +00:00
CONF_PASSWORD,
CONF_USERNAME,
2019-07-31 19:25:30 +00:00
EVENT_HOMEASSISTANT_STOP,
Platform,
2019-07-31 19:25:30 +00:00
)
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
2022-01-10 14:54:09 +00:00
from homeassistant.helpers import config_validation as cv, entity
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import dispatcher_send
from .const import ATTRIBUTION, CONF_POLLING, DOMAIN, LOGGER
2019-07-31 19:25:30 +00:00
SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image"
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
2019-07-31 19:25:30 +00: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 19:25:30 +00:00
ATTR_VALUE = "value"
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
2019-07-31 19:25:30 +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})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.CAMERA,
Platform.COVER,
Platform.LIGHT,
Platform.LOCK,
Platform.SENSOR,
Platform.SWITCH,
]
@dataclass
class AbodeSystem:
"""Abode System class."""
abode: Abode
polling: bool
entity_ids: set[str | None] = field(default_factory=set)
logout_listener: CALLBACK_TYPE | None = None
2022-01-10 14:54:09 +00:00
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Abode integration from a config entry."""
2022-01-10 14:54:09 +00:00
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
polling = entry.data[CONF_POLLING]
2020-11-27 12:39:26 +00:00
# For previous config entries where unique_id is None
2022-01-10 14:54:09 +00:00
if entry.unique_id is None:
2020-11-27 12:39:26 +00:00
hass.config_entries.async_update_entry(
2022-01-10 14:54:09 +00:00
entry, unique_id=entry.data[CONF_USERNAME]
2020-11-27 12:39:26 +00:00
)
try:
abode = await hass.async_add_executor_job(
Abode, username, password, True, True, True
2019-07-31 19:25:30 +00:00
)
2020-11-27 12:39:26 +00: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 12:39:26 +00:00
hass.data[DOMAIN] = AbodeSystem(abode, polling)
await hass.config_entries.async_forward_entry_setups(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 14:54:09 +00: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 14:54:09 +00: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 19:25:30 +00: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 19:25:30 +00:00
]
for entity_id in target_entities:
signal = f"abode_trigger_automation_{entity_id}"
dispatcher_send(hass, signal)
hass.services.register(
2019-07-31 19:25:30 +00:00
DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA
)
hass.services.register(
2019-07-31 19:25:30 +00: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 19:25:30 +00:00
)
async def setup_hass_events(hass: HomeAssistant) -> None:
2017-10-18 16:41:14 +00:00
"""Home Assistant start and stop callbacks."""
2019-07-31 19:25:30 +00:00
2022-01-10 14:54:09 +00: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 14:54:09 +00:00
def event_callback(event: str, event_json: dict[str, str]) -> None:
"""Handle an event callback from Abode."""
data = {
2019-07-31 19:25:30 +00: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 19:25:30 +00:00
ATTR_DATE: event_json.get(ATTR_DATE, ""),
ATTR_TIME: event_json.get(ATTR_TIME, ""),
}
hass.bus.fire(event, data)
2019-07-31 19:25:30 +00:00
events = [
GROUPS.ALARM,
GROUPS.ALARM_END,
GROUPS.PANEL_FAULT,
GROUPS.PANEL_RESTORE,
GROUPS.AUTOMATION,
GROUPS.DISARM,
GROUPS.ARM,
GROUPS.ARM_FAULT,
GROUPS.TEST,
GROUPS.CAPTURE,
GROUPS.DEVICE,
2019-07-31 19:25:30 +00:00
]
for event in events:
hass.data[DOMAIN].abode.events.add_event_callback(
2019-07-31 19:25:30 +00:00
event, partial(event_callback, event)
)
2022-01-10 14:54:09 +00:00
class AbodeEntity(entity.Entity):
"""Representation of an Abode entity."""
_attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
2022-01-10 14:54:09 +00:00
def __init__(self, data: AbodeSystem) -> None:
"""Initialize Abode entity."""
self._data = data
self._attr_should_poll = data.polling
2022-01-10 14:54:09 +00: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 14:54:09 +00: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 14:54:09 +00: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 14:54:09 +00:00
def __init__(self, data: AbodeSystem, device: AbodeDev) -> None:
"""Initialize Abode device."""
super().__init__(data)
self._device = device
2023-08-03 07:10:31 +00:00
self._attr_unique_id = device.uuid
2022-01-10 14:54:09 +00: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,
2023-08-03 07:10:31 +00:00
self._device.id,
2019-07-31 19:25:30 +00:00
self._update_callback,
)
2022-01-10 14:54:09 +00: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(
2023-08-03 07:10:31 +00:00
self._data.abode.events.remove_all_device_callbacks, self._device.id
)
2022-01-10 14:54:09 +00:00
def update(self) -> None:
"""Update device state."""
self._device.refresh()
@property
2022-01-10 14:54:09 +00:00
def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes."""
return {
2023-08-03 07:10:31 +00:00
"device_id": self._device.id,
2019-07-31 19:25:30 +00:00
"battery_low": self._device.battery_low,
"no_response": self._device.no_response,
"device_type": self._device.type,
}
@property
def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity."""
return DeviceInfo(
2023-08-03 07:10:31 +00:00
identifiers={(DOMAIN, self._device.id)},
2021-10-22 14:35:39 +00:00
manufacturer="Abode",
model=self._device.type,
name=self._device.name,
)
2022-01-10 14:54:09 +00: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 14:54:09 +00: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 14:54:09 +00:00
def update(self) -> None:
"""Update automation state."""
self._automation.refresh()