2019-02-13 20:21:14 +00:00
|
|
|
"""Support for Verisure devices."""
|
2021-03-05 23:37:56 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2019-12-09 10:21:16 +00:00
|
|
|
from datetime import timedelta
|
2021-03-05 23:37:56 +00:00
|
|
|
from typing import Any, Literal
|
2019-11-21 21:53:02 +00:00
|
|
|
|
2019-10-12 19:55:33 +00:00
|
|
|
from jsonpath import jsonpath
|
2021-03-11 18:41:01 +00:00
|
|
|
from verisure import (
|
|
|
|
Error as VerisureError,
|
|
|
|
ResponseError as VerisureResponseError,
|
|
|
|
Session as Verisure,
|
|
|
|
)
|
2016-09-07 01:18:34 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
from homeassistant.const import (
|
|
|
|
CONF_PASSWORD,
|
|
|
|
CONF_SCAN_INTERVAL,
|
|
|
|
CONF_USERNAME,
|
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
2020-09-15 17:01:07 +00:00
|
|
|
HTTP_SERVICE_UNAVAILABLE,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2021-03-05 23:37:56 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
2016-09-07 01:18:34 +00:00
|
|
|
from homeassistant.helpers import discovery
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
2021-03-05 23:37:56 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType
|
2021-03-11 18:41:01 +00:00
|
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
2019-12-09 10:21:16 +00:00
|
|
|
from homeassistant.util import Throttle
|
2015-08-15 11:36:30 +00:00
|
|
|
|
2020-12-30 08:55:18 +00:00
|
|
|
from .const import (
|
|
|
|
ATTR_DEVICE_SERIAL,
|
|
|
|
CONF_ALARM,
|
|
|
|
CONF_CODE_DIGITS,
|
|
|
|
CONF_DEFAULT_LOCK_CODE,
|
|
|
|
CONF_DOOR_WINDOW,
|
|
|
|
CONF_GIID,
|
|
|
|
CONF_HYDROMETERS,
|
|
|
|
CONF_LOCKS,
|
|
|
|
CONF_MOUSE,
|
|
|
|
CONF_SMARTCAM,
|
|
|
|
CONF_SMARTPLUGS,
|
|
|
|
CONF_THERMOMETERS,
|
|
|
|
DEFAULT_SCAN_INTERVAL,
|
|
|
|
DOMAIN,
|
|
|
|
LOGGER,
|
|
|
|
MIN_SCAN_INTERVAL,
|
|
|
|
SERVICE_CAPTURE_SMARTCAM,
|
|
|
|
SERVICE_DISABLE_AUTOLOCK,
|
|
|
|
SERVICE_ENABLE_AUTOLOCK,
|
|
|
|
)
|
2016-09-07 01:18:34 +00:00
|
|
|
|
2021-03-02 20:43:59 +00:00
|
|
|
PLATFORMS = [
|
|
|
|
"sensor",
|
|
|
|
"switch",
|
|
|
|
"alarm_control_panel",
|
|
|
|
"lock",
|
|
|
|
"camera",
|
|
|
|
"binary_sensor",
|
|
|
|
]
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
DOMAIN: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
|
|
vol.Optional(CONF_ALARM, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_CODE_DIGITS, default=4): cv.positive_int,
|
|
|
|
vol.Optional(CONF_DOOR_WINDOW, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_GIID): cv.string,
|
|
|
|
vol.Optional(CONF_HYDROMETERS, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_LOCKS, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_DEFAULT_LOCK_CODE): cv.string,
|
|
|
|
vol.Optional(CONF_MOUSE, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_SMARTPLUGS, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_THERMOMETERS, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_SMARTCAM, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): (
|
|
|
|
vol.All(cv.time_period, vol.Clamp(min=MIN_SCAN_INTERVAL))
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
|
2019-11-10 07:25:10 +00:00
|
|
|
DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string})
|
2016-12-06 01:51:58 +00:00
|
|
|
|
2015-08-11 07:28:07 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
2021-03-02 20:43:59 +00:00
|
|
|
"""Set up the Verisure integration."""
|
2021-03-11 18:41:01 +00:00
|
|
|
verisure = Verisure(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD])
|
|
|
|
coordinator = VerisureDataUpdateCoordinator(
|
|
|
|
hass, session=verisure, domain_config=config[DOMAIN]
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2021-03-11 18:41:01 +00:00
|
|
|
|
|
|
|
if not await hass.async_add_executor_job(coordinator.login):
|
|
|
|
LOGGER.error("Login failed")
|
|
|
|
return False
|
|
|
|
|
|
|
|
hass.bus.async_listen_once(
|
|
|
|
EVENT_HOMEASSISTANT_STOP, lambda event: coordinator.logout()
|
|
|
|
)
|
|
|
|
|
|
|
|
await coordinator.async_refresh()
|
|
|
|
if not coordinator.last_update_success:
|
|
|
|
LOGGER.error("Update failed")
|
2015-08-15 11:36:30 +00:00
|
|
|
return False
|
2021-03-11 18:41:01 +00:00
|
|
|
|
|
|
|
hass.data[DOMAIN] = coordinator
|
2015-08-11 07:28:07 +00:00
|
|
|
|
2021-03-02 20:43:59 +00:00
|
|
|
for platform in PLATFORMS:
|
2021-03-11 18:41:01 +00:00
|
|
|
hass.async_create_task(
|
|
|
|
discovery.async_load_platform(hass, platform, DOMAIN, {}, config)
|
|
|
|
)
|
2015-08-11 07:28:07 +00:00
|
|
|
|
2019-11-10 07:25:10 +00:00
|
|
|
async def capture_smartcam(service):
|
2016-12-06 01:51:58 +00:00
|
|
|
"""Capture a new picture from a smartcam."""
|
2019-11-10 07:25:10 +00:00
|
|
|
device_id = service.data[ATTR_DEVICE_SERIAL]
|
|
|
|
try:
|
2021-03-11 18:41:01 +00:00
|
|
|
await hass.async_add_executor_job(coordinator.smartcam_capture, device_id)
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL)
|
2021-03-11 18:41:01 +00:00
|
|
|
except VerisureError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not capture image, %s", ex)
|
2016-12-06 01:51:58 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
hass.services.async_register(
|
2019-11-10 07:25:10 +00:00
|
|
|
DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-12-06 01:51:58 +00:00
|
|
|
|
2019-11-10 07:25:10 +00:00
|
|
|
async def disable_autolock(service):
|
|
|
|
"""Disable autolock on a doorlock."""
|
|
|
|
device_id = service.data[ATTR_DEVICE_SERIAL]
|
|
|
|
try:
|
2021-03-11 18:41:01 +00:00
|
|
|
await hass.async_add_executor_job(coordinator.disable_autolock, device_id)
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL)
|
2021-03-11 18:41:01 +00:00
|
|
|
except VerisureError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not disable autolock, %s", ex)
|
2019-11-10 07:25:10 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
hass.services.async_register(
|
2019-11-10 07:25:10 +00:00
|
|
|
DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA
|
|
|
|
)
|
|
|
|
|
|
|
|
async def enable_autolock(service):
|
|
|
|
"""Enable autolock on a doorlock."""
|
|
|
|
device_id = service.data[ATTR_DEVICE_SERIAL]
|
|
|
|
try:
|
2021-03-11 18:41:01 +00:00
|
|
|
await hass.async_add_executor_job(coordinator.enable_autolock, device_id)
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL)
|
2021-03-11 18:41:01 +00:00
|
|
|
except VerisureError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not enable autolock, %s", ex)
|
2019-11-10 07:25:10 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
hass.services.async_register(
|
2019-11-10 07:25:10 +00:00
|
|
|
DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA
|
|
|
|
)
|
2015-08-11 07:28:07 +00:00
|
|
|
return True
|
2015-08-12 11:00:47 +00:00
|
|
|
|
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
|
|
|
"""A Verisure Data Update Coordinator."""
|
2016-03-08 16:55:57 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
def __init__(
|
|
|
|
self, hass: HomeAssistant, domain_config: ConfigType, session: Verisure
|
|
|
|
) -> None:
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Initialize the Verisure hub."""
|
2017-06-26 20:30:25 +00:00
|
|
|
self.imageseries = {}
|
2016-02-27 20:50:19 +00:00
|
|
|
self.config = domain_config
|
2021-03-11 18:41:01 +00:00
|
|
|
self.giid = domain_config.get(CONF_GIID)
|
2016-02-27 20:50:19 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
self.session = session
|
2016-02-27 20:50:19 +00:00
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
super().__init__(
|
|
|
|
hass, LOGGER, name=DOMAIN, update_interval=domain_config[CONF_SCAN_INTERVAL]
|
|
|
|
)
|
2017-11-14 14:53:26 +00:00
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def login(self) -> bool:
|
2017-06-26 20:30:25 +00:00
|
|
|
"""Login to Verisure."""
|
2016-02-27 20:50:19 +00:00
|
|
|
try:
|
2017-06-26 20:30:25 +00:00
|
|
|
self.session.login()
|
2021-03-11 18:41:01 +00:00
|
|
|
except VerisureError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not log in to verisure, %s", ex)
|
2016-02-27 20:50:19 +00:00
|
|
|
return False
|
2017-11-14 14:53:26 +00:00
|
|
|
if self.giid:
|
|
|
|
return self.set_giid()
|
2016-02-27 20:50:19 +00:00
|
|
|
return True
|
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def logout(self) -> bool:
|
2017-06-26 20:30:25 +00:00
|
|
|
"""Logout from Verisure."""
|
|
|
|
try:
|
|
|
|
self.session.logout()
|
2021-03-11 18:41:01 +00:00
|
|
|
except VerisureError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not log out from verisure, %s", ex)
|
2017-06-26 20:30:25 +00:00
|
|
|
return False
|
|
|
|
return True
|
2016-02-27 20:50:19 +00:00
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def set_giid(self) -> bool:
|
2017-11-14 14:53:26 +00:00
|
|
|
"""Set installation GIID."""
|
|
|
|
try:
|
|
|
|
self.session.set_giid(self.giid)
|
2021-03-11 18:41:01 +00:00
|
|
|
except VerisureError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not set installation GIID, %s", ex)
|
2017-11-14 14:53:26 +00:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2021-03-11 18:41:01 +00:00
|
|
|
async def _async_update_data(self) -> dict:
|
|
|
|
"""Fetch data from Verisure."""
|
2017-06-26 20:30:25 +00:00
|
|
|
try:
|
2021-03-11 18:41:01 +00:00
|
|
|
return await self.hass.async_add_executor_job(self.session.get_overview)
|
|
|
|
except VerisureResponseError as ex:
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.error("Could not read overview, %s", ex)
|
2020-09-15 17:01:07 +00:00
|
|
|
if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable
|
2020-12-30 08:55:18 +00:00
|
|
|
LOGGER.info("Trying to log in again")
|
2021-03-11 18:41:01 +00:00
|
|
|
await self.hass.async_add_executor_job(self.login)
|
|
|
|
return {}
|
|
|
|
raise
|
2016-02-27 20:50:19 +00:00
|
|
|
|
|
|
|
@Throttle(timedelta(seconds=60))
|
2021-03-05 23:37:56 +00:00
|
|
|
def update_smartcam_imageseries(self) -> None:
|
2017-06-26 20:30:25 +00:00
|
|
|
"""Update the image series."""
|
|
|
|
self.imageseries = self.session.get_camera_imageseries()
|
2016-10-21 20:41:17 +00:00
|
|
|
|
2016-12-06 01:51:58 +00:00
|
|
|
@Throttle(timedelta(seconds=30))
|
2021-03-05 23:37:56 +00:00
|
|
|
def smartcam_capture(self, device_id: str) -> None:
|
2016-12-06 01:51:58 +00:00
|
|
|
"""Capture a new image from a smartcam."""
|
2017-06-26 20:30:25 +00:00
|
|
|
self.session.capture_image(device_id)
|
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def disable_autolock(self, device_id: str) -> None:
|
2019-11-10 07:25:10 +00:00
|
|
|
"""Disable autolock."""
|
|
|
|
self.session.set_lock_config(device_id, auto_lock_enabled=False)
|
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def enable_autolock(self, device_id: str) -> None:
|
2019-11-10 07:25:10 +00:00
|
|
|
"""Enable autolock."""
|
|
|
|
self.session.set_lock_config(device_id, auto_lock_enabled=True)
|
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def get(self, jpath: str, *args) -> list[Any] | Literal[False]:
|
2017-06-26 20:30:25 +00:00
|
|
|
"""Get values from the overview that matches the jsonpath."""
|
2021-03-11 18:41:01 +00:00
|
|
|
res = jsonpath(self.data, jpath % args)
|
2020-12-30 08:55:18 +00:00
|
|
|
return res or []
|
2017-06-26 20:30:25 +00:00
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def get_first(self, jpath: str, *args) -> Any | None:
|
2017-06-26 20:30:25 +00:00
|
|
|
"""Get first value from the overview that matches the jsonpath."""
|
|
|
|
res = self.get(jpath, *args)
|
|
|
|
return res[0] if res else None
|
|
|
|
|
2021-03-05 23:37:56 +00:00
|
|
|
def get_image_info(self, jpath: str, *args) -> list[Any] | Literal[False]:
|
2017-06-26 20:30:25 +00:00
|
|
|
"""Get values from the imageseries that matches the jsonpath."""
|
2019-10-12 19:55:33 +00:00
|
|
|
res = jsonpath(self.imageseries, jpath % args)
|
2020-12-30 08:55:18 +00:00
|
|
|
return res or []
|