Add DataUpdateCoordinator to Verisure (#47574)
This commit is contained in:
parent
10848b9bdf
commit
1095905f8c
11 changed files with 287 additions and 475 deletions
|
@ -5,7 +5,11 @@ from datetime import timedelta
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from jsonpath import jsonpath
|
from jsonpath import jsonpath
|
||||||
import verisure
|
from verisure import (
|
||||||
|
Error as VerisureError,
|
||||||
|
ResponseError as VerisureResponseError,
|
||||||
|
Session as Verisure,
|
||||||
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -19,6 +23,7 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -52,8 +57,6 @@ PLATFORMS = [
|
||||||
"binary_sensor",
|
"binary_sensor",
|
||||||
]
|
]
|
||||||
|
|
||||||
HUB = None
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.Schema(
|
||||||
|
@ -83,31 +86,43 @@ CONFIG_SCHEMA = vol.Schema(
|
||||||
DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string})
|
DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string})
|
||||||
|
|
||||||
|
|
||||||
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the Verisure integration."""
|
"""Set up the Verisure integration."""
|
||||||
global HUB # pylint: disable=global-statement
|
verisure = Verisure(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD])
|
||||||
HUB = VerisureHub(config[DOMAIN])
|
coordinator = VerisureDataUpdateCoordinator(
|
||||||
HUB.update_overview = Throttle(config[DOMAIN][CONF_SCAN_INTERVAL])(
|
hass, session=verisure, domain_config=config[DOMAIN]
|
||||||
HUB.update_overview
|
|
||||||
)
|
)
|
||||||
if not HUB.login():
|
|
||||||
|
if not await hass.async_add_executor_job(coordinator.login):
|
||||||
|
LOGGER.error("Login failed")
|
||||||
return False
|
return False
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: HUB.logout())
|
|
||||||
HUB.update_overview()
|
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")
|
||||||
|
return False
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = coordinator
|
||||||
|
|
||||||
for platform in PLATFORMS:
|
for platform in PLATFORMS:
|
||||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
hass.async_create_task(
|
||||||
|
discovery.async_load_platform(hass, platform, DOMAIN, {}, config)
|
||||||
|
)
|
||||||
|
|
||||||
async def capture_smartcam(service):
|
async def capture_smartcam(service):
|
||||||
"""Capture a new picture from a smartcam."""
|
"""Capture a new picture from a smartcam."""
|
||||||
device_id = service.data[ATTR_DEVICE_SERIAL]
|
device_id = service.data[ATTR_DEVICE_SERIAL]
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(HUB.smartcam_capture, device_id)
|
await hass.async_add_executor_job(coordinator.smartcam_capture, device_id)
|
||||||
LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL)
|
LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL)
|
||||||
except verisure.Error as ex:
|
except VerisureError as ex:
|
||||||
LOGGER.error("Could not capture image, %s", ex)
|
LOGGER.error("Could not capture image, %s", ex)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA
|
DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -115,12 +130,12 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Disable autolock on a doorlock."""
|
"""Disable autolock on a doorlock."""
|
||||||
device_id = service.data[ATTR_DEVICE_SERIAL]
|
device_id = service.data[ATTR_DEVICE_SERIAL]
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(HUB.disable_autolock, device_id)
|
await hass.async_add_executor_job(coordinator.disable_autolock, device_id)
|
||||||
LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL)
|
LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL)
|
||||||
except verisure.Error as ex:
|
except VerisureError as ex:
|
||||||
LOGGER.error("Could not disable autolock, %s", ex)
|
LOGGER.error("Could not disable autolock, %s", ex)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA
|
DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -128,38 +143,39 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Enable autolock on a doorlock."""
|
"""Enable autolock on a doorlock."""
|
||||||
device_id = service.data[ATTR_DEVICE_SERIAL]
|
device_id = service.data[ATTR_DEVICE_SERIAL]
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(HUB.enable_autolock, device_id)
|
await hass.async_add_executor_job(coordinator.enable_autolock, device_id)
|
||||||
LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL)
|
LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL)
|
||||||
except verisure.Error as ex:
|
except VerisureError as ex:
|
||||||
LOGGER.error("Could not enable autolock, %s", ex)
|
LOGGER.error("Could not enable autolock, %s", ex)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA
|
DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class VerisureHub:
|
class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
"""A Verisure hub wrapper class."""
|
"""A Verisure Data Update Coordinator."""
|
||||||
|
|
||||||
def __init__(self, domain_config: ConfigType):
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, domain_config: ConfigType, session: Verisure
|
||||||
|
) -> None:
|
||||||
"""Initialize the Verisure hub."""
|
"""Initialize the Verisure hub."""
|
||||||
self.overview = {}
|
|
||||||
self.imageseries = {}
|
self.imageseries = {}
|
||||||
|
|
||||||
self.config = domain_config
|
self.config = domain_config
|
||||||
|
|
||||||
self.session = verisure.Session(
|
|
||||||
domain_config[CONF_USERNAME], domain_config[CONF_PASSWORD]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.giid = domain_config.get(CONF_GIID)
|
self.giid = domain_config.get(CONF_GIID)
|
||||||
|
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
hass, LOGGER, name=DOMAIN, update_interval=domain_config[CONF_SCAN_INTERVAL]
|
||||||
|
)
|
||||||
|
|
||||||
def login(self) -> bool:
|
def login(self) -> bool:
|
||||||
"""Login to Verisure."""
|
"""Login to Verisure."""
|
||||||
try:
|
try:
|
||||||
self.session.login()
|
self.session.login()
|
||||||
except verisure.Error as ex:
|
except VerisureError as ex:
|
||||||
LOGGER.error("Could not log in to verisure, %s", ex)
|
LOGGER.error("Could not log in to verisure, %s", ex)
|
||||||
return False
|
return False
|
||||||
if self.giid:
|
if self.giid:
|
||||||
|
@ -170,7 +186,7 @@ class VerisureHub:
|
||||||
"""Logout from Verisure."""
|
"""Logout from Verisure."""
|
||||||
try:
|
try:
|
||||||
self.session.logout()
|
self.session.logout()
|
||||||
except verisure.Error as ex:
|
except VerisureError as ex:
|
||||||
LOGGER.error("Could not log out from verisure, %s", ex)
|
LOGGER.error("Could not log out from verisure, %s", ex)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -179,22 +195,22 @@ class VerisureHub:
|
||||||
"""Set installation GIID."""
|
"""Set installation GIID."""
|
||||||
try:
|
try:
|
||||||
self.session.set_giid(self.giid)
|
self.session.set_giid(self.giid)
|
||||||
except verisure.Error as ex:
|
except VerisureError as ex:
|
||||||
LOGGER.error("Could not set installation GIID, %s", ex)
|
LOGGER.error("Could not set installation GIID, %s", ex)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update_overview(self) -> None:
|
async def _async_update_data(self) -> dict:
|
||||||
"""Update the overview."""
|
"""Fetch data from Verisure."""
|
||||||
try:
|
try:
|
||||||
self.overview = self.session.get_overview()
|
return await self.hass.async_add_executor_job(self.session.get_overview)
|
||||||
except verisure.ResponseError as ex:
|
except VerisureResponseError as ex:
|
||||||
LOGGER.error("Could not read overview, %s", ex)
|
LOGGER.error("Could not read overview, %s", ex)
|
||||||
if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable
|
if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable
|
||||||
LOGGER.info("Trying to log in again")
|
LOGGER.info("Trying to log in again")
|
||||||
self.login()
|
await self.hass.async_add_executor_job(self.login)
|
||||||
else:
|
return {}
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@Throttle(timedelta(seconds=60))
|
@Throttle(timedelta(seconds=60))
|
||||||
def update_smartcam_imageseries(self) -> None:
|
def update_smartcam_imageseries(self) -> None:
|
||||||
|
@ -216,7 +232,7 @@ class VerisureHub:
|
||||||
|
|
||||||
def get(self, jpath: str, *args) -> list[Any] | Literal[False]:
|
def get(self, jpath: str, *args) -> list[Any] | Literal[False]:
|
||||||
"""Get values from the overview that matches the jsonpath."""
|
"""Get values from the overview that matches the jsonpath."""
|
||||||
res = jsonpath(self.overview, jpath % args)
|
res = jsonpath(self.data, jpath % args)
|
||||||
return res or []
|
return res or []
|
||||||
|
|
||||||
def get_first(self, jpath: str, *args) -> Any | None:
|
def get_first(self, jpath: str, *args) -> Any | None:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Support for Verisure alarm control panels."""
|
"""Support for Verisure alarm control panels."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from time import sleep
|
import asyncio
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from homeassistant.components.alarm_control_panel import (
|
from homeassistant.components.alarm_control_panel import (
|
||||||
|
@ -16,12 +16,14 @@ from homeassistant.const import (
|
||||||
STATE_ALARM_ARMED_AWAY,
|
STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_ARMED_HOME,
|
STATE_ALARM_ARMED_HOME,
|
||||||
STATE_ALARM_DISARMED,
|
STATE_ALARM_DISARMED,
|
||||||
|
STATE_ALARM_PENDING,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import HUB as hub
|
from . import VerisureDataUpdateCoordinator
|
||||||
from .const import CONF_ALARM, CONF_CODE_DIGITS, CONF_GIID, LOGGER
|
from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
def setup_platform(
|
||||||
|
@ -31,51 +33,53 @@ def setup_platform(
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Verisure platform."""
|
"""Set up the Verisure platform."""
|
||||||
|
coordinator = hass.data[DOMAIN]
|
||||||
alarms = []
|
alarms = []
|
||||||
if int(hub.config.get(CONF_ALARM, 1)):
|
if int(coordinator.config.get(CONF_ALARM, 1)):
|
||||||
hub.update_overview()
|
alarms.append(VerisureAlarm(coordinator))
|
||||||
alarms.append(VerisureAlarm())
|
|
||||||
add_entities(alarms)
|
add_entities(alarms)
|
||||||
|
|
||||||
|
|
||||||
def set_arm_state(state: str, code: str | None = None) -> None:
|
class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity):
|
||||||
"""Send set arm state command."""
|
|
||||||
transaction_id = hub.session.set_arm_state(code, state)[
|
|
||||||
"armStateChangeTransactionId"
|
|
||||||
]
|
|
||||||
LOGGER.info("verisure set arm state %s", state)
|
|
||||||
transaction = {}
|
|
||||||
while "result" not in transaction:
|
|
||||||
sleep(0.5)
|
|
||||||
transaction = hub.session.get_arm_state_transaction(transaction_id)
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
|
|
||||||
class VerisureAlarm(AlarmControlPanelEntity):
|
|
||||||
"""Representation of a Verisure alarm status."""
|
"""Representation of a Verisure alarm status."""
|
||||||
|
|
||||||
def __init__(self):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None:
|
||||||
"""Initialize the Verisure alarm panel."""
|
"""Initialize the Verisure alarm panel."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._state = None
|
self._state = None
|
||||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
|
||||||
self._changed_by = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
giid = hub.config.get(CONF_GIID)
|
giid = self.coordinator.config.get(CONF_GIID)
|
||||||
if giid is not None:
|
if giid is not None:
|
||||||
aliass = {i["giid"]: i["alias"] for i in hub.session.installations}
|
aliass = {
|
||||||
|
i["giid"]: i["alias"] for i in self.coordinator.session.installations
|
||||||
|
}
|
||||||
if giid in aliass:
|
if giid in aliass:
|
||||||
return "{} alarm".format(aliass[giid])
|
return "{} alarm".format(aliass[giid])
|
||||||
|
|
||||||
LOGGER.error("Verisure installation giid not found: %s", giid)
|
LOGGER.error("Verisure installation giid not found: %s", giid)
|
||||||
|
|
||||||
return "{} alarm".format(hub.session.installations[0]["alias"])
|
return "{} alarm".format(self.coordinator.session.installations[0]["alias"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
|
status = self.coordinator.get_first("$.armState.statusType")
|
||||||
|
if status == "DISARMED":
|
||||||
|
self._state = STATE_ALARM_DISARMED
|
||||||
|
elif status == "ARMED_HOME":
|
||||||
|
self._state = STATE_ALARM_ARMED_HOME
|
||||||
|
elif status == "ARMED_AWAY":
|
||||||
|
self._state = STATE_ALARM_ARMED_AWAY
|
||||||
|
elif status == "PENDING":
|
||||||
|
self._state = STATE_ALARM_PENDING
|
||||||
|
else:
|
||||||
|
LOGGER.error("Unknown alarm state %s", status)
|
||||||
|
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -91,30 +95,32 @@ class VerisureAlarm(AlarmControlPanelEntity):
|
||||||
@property
|
@property
|
||||||
def changed_by(self) -> str | None:
|
def changed_by(self) -> str | None:
|
||||||
"""Return the last change triggered by."""
|
"""Return the last change triggered by."""
|
||||||
return self._changed_by
|
return self.coordinator.get_first("$.armState.name")
|
||||||
|
|
||||||
def update(self) -> None:
|
async def _async_set_arm_state(self, state: str, code: str | None = None) -> None:
|
||||||
"""Update alarm status."""
|
"""Send set arm state command."""
|
||||||
hub.update_overview()
|
arm_state = await self.hass.async_add_executor_job(
|
||||||
status = hub.get_first("$.armState.statusType")
|
self.coordinator.session.set_arm_state, code, state
|
||||||
if status == "DISARMED":
|
)
|
||||||
self._state = STATE_ALARM_DISARMED
|
LOGGER.debug("Verisure set arm state %s", state)
|
||||||
elif status == "ARMED_HOME":
|
transaction = {}
|
||||||
self._state = STATE_ALARM_ARMED_HOME
|
while "result" not in transaction:
|
||||||
elif status == "ARMED_AWAY":
|
await asyncio.sleep(0.5)
|
||||||
self._state = STATE_ALARM_ARMED_AWAY
|
transaction = await self.hass.async_add_executor_job(
|
||||||
elif status != "PENDING":
|
self.coordinator.session.get_arm_state_transaction,
|
||||||
LOGGER.error("Unknown alarm state %s", status)
|
arm_state["armStateChangeTransactionId"],
|
||||||
self._changed_by = hub.get_first("$.armState.name")
|
)
|
||||||
|
|
||||||
def alarm_disarm(self, code: str | None = None) -> None:
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
|
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
set_arm_state("DISARMED", code)
|
await self._async_set_arm_state("DISARMED", code)
|
||||||
|
|
||||||
def alarm_arm_home(self, code: str | None = None) -> None:
|
async def async_alarm_arm_home(self, code: str | None = None) -> None:
|
||||||
"""Send arm home command."""
|
"""Send arm home command."""
|
||||||
set_arm_state("ARMED_HOME", code)
|
await self._async_set_arm_state("ARMED_HOME", code)
|
||||||
|
|
||||||
def alarm_arm_away(self, code: str | None = None) -> None:
|
async def async_alarm_arm_away(self, code: str | None = None) -> None:
|
||||||
"""Send arm away command."""
|
"""Send arm away command."""
|
||||||
set_arm_state("ARMED_AWAY", code)
|
await self._async_set_arm_state("ARMED_AWAY", code)
|
||||||
|
|
|
@ -9,8 +9,9 @@ from homeassistant.components.binary_sensor import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import CONF_DOOR_WINDOW, HUB as hub
|
from . import CONF_DOOR_WINDOW, DOMAIN, VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
def setup_platform(
|
||||||
|
@ -20,34 +21,39 @@ def setup_platform(
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Verisure binary sensors."""
|
"""Set up the Verisure binary sensors."""
|
||||||
sensors = []
|
coordinator = hass.data[DOMAIN]
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
if int(hub.config.get(CONF_DOOR_WINDOW, 1)):
|
sensors = [VerisureEthernetStatus(coordinator)]
|
||||||
|
|
||||||
|
if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)):
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
VerisureDoorWindowSensor(device_label)
|
VerisureDoorWindowSensor(coordinator, device_label)
|
||||||
for device_label in hub.get(
|
for device_label in coordinator.get(
|
||||||
"$.doorWindow.doorWindowDevice[*].deviceLabel"
|
"$.doorWindow.doorWindowDevice[*].deviceLabel"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
sensors.extend([VerisureEthernetStatus()])
|
|
||||||
add_entities(sensors)
|
add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class VerisureDoorWindowSensor(BinarySensorEntity):
|
class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity):
|
||||||
"""Representation of a Verisure door window sensor."""
|
"""Representation of a Verisure door window sensor."""
|
||||||
|
|
||||||
def __init__(self, device_label: str):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: VerisureDataUpdateCoordinator, device_label: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the Verisure door window sensor."""
|
"""Initialize the Verisure door window sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device_label = device_label
|
self._device_label = device_label
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the binary sensor."""
|
"""Return the name of the binary sensor."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area",
|
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area",
|
||||||
self._device_label,
|
self._device_label,
|
||||||
)
|
)
|
||||||
|
@ -56,7 +62,7 @@ class VerisureDoorWindowSensor(BinarySensorEntity):
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state",
|
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state",
|
||||||
self._device_label,
|
self._device_label,
|
||||||
)
|
)
|
||||||
|
@ -67,22 +73,19 @@ class VerisureDoorWindowSensor(BinarySensorEntity):
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
|
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
|
||||||
self._device_label,
|
self._device_label,
|
||||||
)
|
)
|
||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the state of the sensor."""
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
|
class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity):
|
||||||
class VerisureEthernetStatus(BinarySensorEntity):
|
|
||||||
"""Representation of a Verisure VBOX internet status."""
|
"""Representation of a Verisure VBOX internet status."""
|
||||||
|
|
||||||
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the binary sensor."""
|
"""Return the name of the binary sensor."""
|
||||||
|
@ -91,17 +94,12 @@ class VerisureEthernetStatus(BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return hub.get_first("$.ethernetConnectedNow")
|
return self.coordinator.get_first("$.ethernetConnectedNow")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return hub.get_first("$.ethernetConnectedNow") is not None
|
return self.coordinator.get_first("$.ethernetConnectedNow") is not None
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the state of the sensor."""
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> str:
|
def device_class(self) -> str:
|
||||||
|
|
|
@ -3,15 +3,16 @@ from __future__ import annotations
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
from typing import Any, Callable, Literal
|
from typing import Any, Callable
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera
|
from homeassistant.components.camera import Camera
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import HUB as hub
|
from . import VerisureDataUpdateCoordinator
|
||||||
from .const import CONF_SMARTCAM, LOGGER
|
from .const import CONF_SMARTCAM, DOMAIN, LOGGER
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
def setup_platform(
|
||||||
|
@ -19,31 +20,39 @@ def setup_platform(
|
||||||
config: dict[str, Any],
|
config: dict[str, Any],
|
||||||
add_entities: Callable[[list[Entity], bool], None],
|
add_entities: Callable[[list[Entity], bool], None],
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None | Literal[False]:
|
) -> None:
|
||||||
"""Set up the Verisure Camera."""
|
"""Set up the Verisure Camera."""
|
||||||
if not int(hub.config.get(CONF_SMARTCAM, 1)):
|
coordinator = hass.data[DOMAIN]
|
||||||
return False
|
if not int(coordinator.config.get(CONF_SMARTCAM, 1)):
|
||||||
|
return
|
||||||
|
|
||||||
directory_path = hass.config.config_dir
|
directory_path = hass.config.config_dir
|
||||||
if not os.access(directory_path, os.R_OK):
|
if not os.access(directory_path, os.R_OK):
|
||||||
LOGGER.error("file path %s is not readable", directory_path)
|
LOGGER.error("file path %s is not readable", directory_path)
|
||||||
return False
|
return
|
||||||
|
|
||||||
hub.update_overview()
|
add_entities(
|
||||||
smartcams = [
|
[
|
||||||
VerisureSmartcam(hass, device_label, directory_path)
|
VerisureSmartcam(hass, coordinator, device_label, directory_path)
|
||||||
for device_label in hub.get("$.customerImageCameras[*].deviceLabel")
|
for device_label in coordinator.get("$.customerImageCameras[*].deviceLabel")
|
||||||
]
|
]
|
||||||
|
)
|
||||||
add_entities(smartcams)
|
|
||||||
|
|
||||||
|
|
||||||
class VerisureSmartcam(Camera):
|
class VerisureSmartcam(CoordinatorEntity, Camera):
|
||||||
"""Representation of a Verisure camera."""
|
"""Representation of a Verisure camera."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, device_label: str, directory_path: str):
|
coordinator = VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
coordinator: VerisureDataUpdateCoordinator,
|
||||||
|
device_label: str,
|
||||||
|
directory_path: str,
|
||||||
|
):
|
||||||
"""Initialize Verisure File Camera component."""
|
"""Initialize Verisure File Camera component."""
|
||||||
super().__init__()
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._device_label = device_label
|
self._device_label = device_label
|
||||||
self._directory_path = directory_path
|
self._directory_path = directory_path
|
||||||
|
@ -63,8 +72,8 @@ class VerisureSmartcam(Camera):
|
||||||
|
|
||||||
def check_imagelist(self) -> None:
|
def check_imagelist(self) -> None:
|
||||||
"""Check the contents of the image list."""
|
"""Check the contents of the image list."""
|
||||||
hub.update_smartcam_imageseries()
|
self.coordinator.update_smartcam_imageseries()
|
||||||
image_ids = hub.get_image_info(
|
image_ids = self.coordinator.get_image_info(
|
||||||
"$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label
|
"$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label
|
||||||
)
|
)
|
||||||
if not image_ids:
|
if not image_ids:
|
||||||
|
@ -77,7 +86,9 @@ class VerisureSmartcam(Camera):
|
||||||
new_image_path = os.path.join(
|
new_image_path = os.path.join(
|
||||||
self._directory_path, "{}{}".format(new_image_id, ".jpg")
|
self._directory_path, "{}{}".format(new_image_id, ".jpg")
|
||||||
)
|
)
|
||||||
hub.session.download_image(self._device_label, new_image_id, new_image_path)
|
self.coordinator.session.download_image(
|
||||||
|
self._device_label, new_image_id, new_image_path
|
||||||
|
)
|
||||||
LOGGER.debug("Old image_id=%s", self._image_id)
|
LOGGER.debug("Old image_id=%s", self._image_id)
|
||||||
self.delete_image()
|
self.delete_image()
|
||||||
|
|
||||||
|
@ -99,6 +110,6 @@ class VerisureSmartcam(Camera):
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of this camera."""
|
"""Return the name of this camera."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label
|
"$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
"""Support for Verisure locks."""
|
"""Support for Verisure locks."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from time import monotonic, sleep
|
import asyncio
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from homeassistant.components.lock import LockEntity
|
from homeassistant.components.lock import LockEntity
|
||||||
from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED
|
from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import HUB as hub
|
from . import VerisureDataUpdateCoordinator
|
||||||
from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, LOGGER
|
from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
def setup_platform(
|
||||||
|
@ -20,48 +21,48 @@ def setup_platform(
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Verisure lock platform."""
|
"""Set up the Verisure lock platform."""
|
||||||
|
coordinator = hass.data[DOMAIN]
|
||||||
locks = []
|
locks = []
|
||||||
if int(hub.config.get(CONF_LOCKS, 1)):
|
if int(coordinator.config.get(CONF_LOCKS, 1)):
|
||||||
hub.update_overview()
|
|
||||||
locks.extend(
|
locks.extend(
|
||||||
[
|
[
|
||||||
VerisureDoorlock(device_label)
|
VerisureDoorlock(coordinator, device_label)
|
||||||
for device_label in hub.get("$.doorLockStatusList[*].deviceLabel")
|
for device_label in coordinator.get(
|
||||||
|
"$.doorLockStatusList[*].deviceLabel"
|
||||||
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
add_entities(locks)
|
add_entities(locks)
|
||||||
|
|
||||||
|
|
||||||
class VerisureDoorlock(LockEntity):
|
class VerisureDoorlock(CoordinatorEntity, LockEntity):
|
||||||
"""Representation of a Verisure doorlock."""
|
"""Representation of a Verisure doorlock."""
|
||||||
|
|
||||||
def __init__(self, device_label: str):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: VerisureDataUpdateCoordinator, device_label: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the Verisure lock."""
|
"""Initialize the Verisure lock."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device_label = device_label
|
self._device_label = device_label
|
||||||
self._state = None
|
self._state = None
|
||||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
self._digits = coordinator.config.get(CONF_CODE_DIGITS)
|
||||||
self._changed_by = None
|
self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE)
|
||||||
self._change_timestamp = 0
|
|
||||||
self._default_lock_code = hub.config.get(CONF_DEFAULT_LOCK_CODE)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the lock."""
|
"""Return the name of the lock."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label
|
"$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self) -> str | None:
|
|
||||||
"""Return the state of the lock."""
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.doorLockStatusList[?(@.deviceLabel=='%s')]", self._device_label
|
"$.doorLockStatusList[?(@.deviceLabel=='%s')]", self._device_label
|
||||||
)
|
)
|
||||||
is not None
|
is not None
|
||||||
|
@ -70,78 +71,65 @@ class VerisureDoorlock(LockEntity):
|
||||||
@property
|
@property
|
||||||
def changed_by(self) -> str | None:
|
def changed_by(self) -> str | None:
|
||||||
"""Last change triggered by."""
|
"""Last change triggered by."""
|
||||||
return self._changed_by
|
return self.coordinator.get_first(
|
||||||
|
"$.doorLockStatusList[?(@.deviceLabel=='%s')].userString",
|
||||||
|
self._device_label,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def code_format(self) -> str:
|
def code_format(self) -> str:
|
||||||
"""Return the required six digit code."""
|
"""Return the required six digit code."""
|
||||||
return "^\\d{%s}$" % self._digits
|
return "^\\d{%s}$" % self._digits
|
||||||
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update lock status."""
|
|
||||||
if monotonic() - self._change_timestamp < 10:
|
|
||||||
return
|
|
||||||
hub.update_overview()
|
|
||||||
status = hub.get_first(
|
|
||||||
"$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState",
|
|
||||||
self._device_label,
|
|
||||||
)
|
|
||||||
if status == "UNLOCKED":
|
|
||||||
self._state = STATE_UNLOCKED
|
|
||||||
elif status == "LOCKED":
|
|
||||||
self._state = STATE_LOCKED
|
|
||||||
elif status != "PENDING":
|
|
||||||
LOGGER.error("Unknown lock state %s", status)
|
|
||||||
self._changed_by = hub.get_first(
|
|
||||||
"$.doorLockStatusList[?(@.deviceLabel=='%s')].userString",
|
|
||||||
self._device_label,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_locked(self) -> bool:
|
def is_locked(self) -> bool:
|
||||||
"""Return true if lock is locked."""
|
"""Return true if lock is locked."""
|
||||||
return self._state == STATE_LOCKED
|
status = self.coordinator.get_first(
|
||||||
|
"$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState",
|
||||||
|
self._device_label,
|
||||||
|
)
|
||||||
|
return status == "LOCKED"
|
||||||
|
|
||||||
def unlock(self, **kwargs) -> None:
|
async def async_unlock(self, **kwargs) -> None:
|
||||||
"""Send unlock command."""
|
"""Send unlock command."""
|
||||||
if self._state is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
code = kwargs.get(ATTR_CODE, self._default_lock_code)
|
code = kwargs.get(ATTR_CODE, self._default_lock_code)
|
||||||
if code is None:
|
if code is None:
|
||||||
LOGGER.error("Code required but none provided")
|
LOGGER.error("Code required but none provided")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.set_lock_state(code, STATE_UNLOCKED)
|
await self.async_set_lock_state(code, STATE_UNLOCKED)
|
||||||
|
|
||||||
def lock(self, **kwargs) -> None:
|
async def async_lock(self, **kwargs) -> None:
|
||||||
"""Send lock command."""
|
"""Send lock command."""
|
||||||
if self._state == STATE_LOCKED:
|
|
||||||
return
|
|
||||||
|
|
||||||
code = kwargs.get(ATTR_CODE, self._default_lock_code)
|
code = kwargs.get(ATTR_CODE, self._default_lock_code)
|
||||||
if code is None:
|
if code is None:
|
||||||
LOGGER.error("Code required but none provided")
|
LOGGER.error("Code required but none provided")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.set_lock_state(code, STATE_LOCKED)
|
await self.async_set_lock_state(code, STATE_LOCKED)
|
||||||
|
|
||||||
def set_lock_state(self, code: str, state: str) -> None:
|
async def async_set_lock_state(self, code: str, state: str) -> None:
|
||||||
"""Send set lock state command."""
|
"""Send set lock state command."""
|
||||||
lock_state = "lock" if state == STATE_LOCKED else "unlock"
|
target_state = "lock" if state == STATE_LOCKED else "unlock"
|
||||||
transaction_id = hub.session.set_lock_state(
|
lock_state = await self.hass.async_add_executor_job(
|
||||||
code, self._device_label, lock_state
|
self.coordinator.session.set_lock_state,
|
||||||
)["doorLockStateChangeTransactionId"]
|
code,
|
||||||
|
self._device_label,
|
||||||
|
target_state,
|
||||||
|
)
|
||||||
|
|
||||||
LOGGER.debug("Verisure doorlock %s", state)
|
LOGGER.debug("Verisure doorlock %s", state)
|
||||||
transaction = {}
|
transaction = {}
|
||||||
attempts = 0
|
attempts = 0
|
||||||
while "result" not in transaction:
|
while "result" not in transaction:
|
||||||
transaction = hub.session.get_lock_state_transaction(transaction_id)
|
transaction = await self.hass.async_add_executor_job(
|
||||||
|
self.coordinator.session.get_lock_state_transaction,
|
||||||
|
lock_state["doorLockStateChangeTransactionId"],
|
||||||
|
)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
if attempts == 30:
|
if attempts == 30:
|
||||||
break
|
break
|
||||||
if attempts > 1:
|
if attempts > 1:
|
||||||
sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
if transaction["result"] == "OK":
|
if transaction["result"] == "OK":
|
||||||
self._state = state
|
self._state = state
|
||||||
self._change_timestamp = monotonic()
|
|
||||||
|
|
|
@ -6,9 +6,10 @@ from typing import Any, Callable
|
||||||
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
|
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import HUB as hub
|
from . import VerisureDataUpdateCoordinator
|
||||||
from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS
|
from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
def setup_platform(
|
||||||
|
@ -18,34 +19,34 @@ def setup_platform(
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Verisure platform."""
|
"""Set up the Verisure platform."""
|
||||||
sensors = []
|
coordinator = hass.data[DOMAIN]
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
if int(hub.config.get(CONF_THERMOMETERS, 1)):
|
sensors = []
|
||||||
|
if int(coordinator.config.get(CONF_THERMOMETERS, 1)):
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
VerisureThermometer(device_label)
|
VerisureThermometer(coordinator, device_label)
|
||||||
for device_label in hub.get(
|
for device_label in coordinator.get(
|
||||||
"$.climateValues[?(@.temperature)].deviceLabel"
|
"$.climateValues[?(@.temperature)].deviceLabel"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if int(hub.config.get(CONF_HYDROMETERS, 1)):
|
if int(coordinator.config.get(CONF_HYDROMETERS, 1)):
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
VerisureHygrometer(device_label)
|
VerisureHygrometer(coordinator, device_label)
|
||||||
for device_label in hub.get(
|
for device_label in coordinator.get(
|
||||||
"$.climateValues[?(@.humidity)].deviceLabel"
|
"$.climateValues[?(@.humidity)].deviceLabel"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if int(hub.config.get(CONF_MOUSE, 1)):
|
if int(coordinator.config.get(CONF_MOUSE, 1)):
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
VerisureMouseDetection(device_label)
|
VerisureMouseDetection(coordinator, device_label)
|
||||||
for device_label in hub.get(
|
for device_label in coordinator.get(
|
||||||
"$.eventCounts[?(@.deviceType=='MOUSE1')].deviceLabel"
|
"$.eventCounts[?(@.deviceType=='MOUSE1')].deviceLabel"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -54,18 +55,23 @@ def setup_platform(
|
||||||
add_entities(sensors)
|
add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class VerisureThermometer(Entity):
|
class VerisureThermometer(CoordinatorEntity, Entity):
|
||||||
"""Representation of a Verisure thermometer."""
|
"""Representation of a Verisure thermometer."""
|
||||||
|
|
||||||
def __init__(self, device_label: str):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: VerisureDataUpdateCoordinator, device_label: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device_label = device_label
|
self._device_label = device_label
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label
|
"$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label
|
||||||
)
|
)
|
||||||
+ " temperature"
|
+ " temperature"
|
||||||
|
@ -74,7 +80,7 @@ class VerisureThermometer(Entity):
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label
|
"$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,7 +88,7 @@ class VerisureThermometer(Entity):
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.climateValues[?(@.deviceLabel=='%s')].temperature",
|
"$.climateValues[?(@.deviceLabel=='%s')].temperature",
|
||||||
self._device_label,
|
self._device_label,
|
||||||
)
|
)
|
||||||
|
@ -94,24 +100,24 @@ class VerisureThermometer(Entity):
|
||||||
"""Return the unit of measurement of this entity."""
|
"""Return the unit of measurement of this entity."""
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the sensor."""
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
|
class VerisureHygrometer(CoordinatorEntity, Entity):
|
||||||
class VerisureHygrometer(Entity):
|
|
||||||
"""Representation of a Verisure hygrometer."""
|
"""Representation of a Verisure hygrometer."""
|
||||||
|
|
||||||
def __init__(self, device_label: str):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: VerisureDataUpdateCoordinator, device_label: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device_label = device_label
|
self._device_label = device_label
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label
|
"$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label
|
||||||
)
|
)
|
||||||
+ " humidity"
|
+ " humidity"
|
||||||
|
@ -120,7 +126,7 @@ class VerisureHygrometer(Entity):
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label
|
"$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -128,7 +134,7 @@ class VerisureHygrometer(Entity):
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label
|
"$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label
|
||||||
)
|
)
|
||||||
is not None
|
is not None
|
||||||
|
@ -139,24 +145,24 @@ class VerisureHygrometer(Entity):
|
||||||
"""Return the unit of measurement of this entity."""
|
"""Return the unit of measurement of this entity."""
|
||||||
return PERCENTAGE
|
return PERCENTAGE
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the sensor."""
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
||||||
|
class VerisureMouseDetection(CoordinatorEntity, Entity):
|
||||||
class VerisureMouseDetection(Entity):
|
|
||||||
"""Representation of a Verisure mouse detector."""
|
"""Representation of a Verisure mouse detector."""
|
||||||
|
|
||||||
def __init__(self, device_label):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: VerisureDataUpdateCoordinator, device_label: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device_label = device_label
|
self._device_label = device_label
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return (
|
return (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.eventCounts[?(@.deviceLabel=='%s')].area", self._device_label
|
"$.eventCounts[?(@.deviceLabel=='%s')].area", self._device_label
|
||||||
)
|
)
|
||||||
+ " mouse"
|
+ " mouse"
|
||||||
|
@ -165,7 +171,7 @@ class VerisureMouseDetection(Entity):
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label
|
"$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -173,7 +179,9 @@ class VerisureMouseDetection(Entity):
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
hub.get_first("$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label)
|
self.coordinator.get_first(
|
||||||
|
"$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label
|
||||||
|
)
|
||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -181,8 +189,3 @@ class VerisureMouseDetection(Entity):
|
||||||
def unit_of_measurement(self) -> str:
|
def unit_of_measurement(self) -> str:
|
||||||
"""Return the unit of measurement of this entity."""
|
"""Return the unit of measurement of this entity."""
|
||||||
return "Mice"
|
return "Mice"
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the sensor."""
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
from typing import Any, Callable, Literal
|
from typing import Any, Callable
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import CONF_SMARTPLUGS, HUB as hub
|
from . import VerisureDataUpdateCoordinator
|
||||||
|
from .const import CONF_SMARTPLUGS, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
def setup_platform(
|
||||||
|
@ -16,25 +18,31 @@ def setup_platform(
|
||||||
config: dict[str, Any],
|
config: dict[str, Any],
|
||||||
add_entities: Callable[[list[Entity], bool], None],
|
add_entities: Callable[[list[Entity], bool], None],
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None | Literal[False]:
|
) -> None:
|
||||||
"""Set up the Verisure switch platform."""
|
"""Set up the Verisure switch platform."""
|
||||||
if not int(hub.config.get(CONF_SMARTPLUGS, 1)):
|
coordinator = hass.data[DOMAIN]
|
||||||
return False
|
|
||||||
|
|
||||||
hub.update_overview()
|
if not int(coordinator.config.get(CONF_SMARTPLUGS, 1)):
|
||||||
switches = [
|
return
|
||||||
VerisureSmartplug(device_label)
|
|
||||||
for device_label in hub.get("$.smartPlugs[*].deviceLabel")
|
|
||||||
]
|
|
||||||
|
|
||||||
add_entities(switches)
|
add_entities(
|
||||||
|
[
|
||||||
|
VerisureSmartplug(coordinator, device_label)
|
||||||
|
for device_label in coordinator.get("$.smartPlugs[*].deviceLabel")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VerisureSmartplug(SwitchEntity):
|
class VerisureSmartplug(CoordinatorEntity, SwitchEntity):
|
||||||
"""Representation of a Verisure smartplug."""
|
"""Representation of a Verisure smartplug."""
|
||||||
|
|
||||||
def __init__(self, device_id: str):
|
coordinator: VerisureDataUpdateCoordinator
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: VerisureDataUpdateCoordinator, device_id: str
|
||||||
|
) -> None:
|
||||||
"""Initialize the Verisure device."""
|
"""Initialize the Verisure device."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._device_label = device_id
|
self._device_label = device_id
|
||||||
self._change_timestamp = 0
|
self._change_timestamp = 0
|
||||||
self._state = False
|
self._state = False
|
||||||
|
@ -42,7 +50,7 @@ class VerisureSmartplug(SwitchEntity):
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name or location of the smartplug."""
|
"""Return the name or location of the smartplug."""
|
||||||
return hub.get_first(
|
return self.coordinator.get_first(
|
||||||
"$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label
|
"$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,7 +60,7 @@ class VerisureSmartplug(SwitchEntity):
|
||||||
if monotonic() - self._change_timestamp < 10:
|
if monotonic() - self._change_timestamp < 10:
|
||||||
return self._state
|
return self._state
|
||||||
self._state = (
|
self._state = (
|
||||||
hub.get_first(
|
self.coordinator.get_first(
|
||||||
"$.smartPlugs[?(@.deviceLabel == '%s')].currentState",
|
"$.smartPlugs[?(@.deviceLabel == '%s')].currentState",
|
||||||
self._device_label,
|
self._device_label,
|
||||||
)
|
)
|
||||||
|
@ -64,23 +72,20 @@ class VerisureSmartplug(SwitchEntity):
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
hub.get_first("$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label)
|
self.coordinator.get_first(
|
||||||
|
"$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label
|
||||||
|
)
|
||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
def turn_on(self, **kwargs) -> None:
|
def turn_on(self, **kwargs) -> None:
|
||||||
"""Set smartplug status on."""
|
"""Set smartplug status on."""
|
||||||
hub.session.set_smartplug_state(self._device_label, True)
|
self.coordinator.session.set_smartplug_state(self._device_label, True)
|
||||||
self._state = True
|
self._state = True
|
||||||
self._change_timestamp = monotonic()
|
self._change_timestamp = monotonic()
|
||||||
|
|
||||||
def turn_off(self, **kwargs) -> None:
|
def turn_off(self, **kwargs) -> None:
|
||||||
"""Set smartplug status off."""
|
"""Set smartplug status off."""
|
||||||
hub.session.set_smartplug_state(self._device_label, False)
|
self.coordinator.session.set_smartplug_state(self._device_label, False)
|
||||||
self._state = False
|
self._state = False
|
||||||
self._change_timestamp = monotonic()
|
self._change_timestamp = monotonic()
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Get the latest date of the smartplug."""
|
|
||||||
hub.update_overview()
|
|
||||||
|
|
|
@ -1166,9 +1166,6 @@ uvcclient==0.11.0
|
||||||
# homeassistant.components.vilfo
|
# homeassistant.components.vilfo
|
||||||
vilfo-api-client==0.3.2
|
vilfo-api-client==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.verisure
|
|
||||||
vsure==1.7.2
|
|
||||||
|
|
||||||
# homeassistant.components.vultr
|
# homeassistant.components.vultr
|
||||||
vultr==0.1.2
|
vultr==0.1.2
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
"""Tests for Verisure integration."""
|
|
|
@ -1,67 +0,0 @@
|
||||||
"""Test Verisure ethernet status."""
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN
|
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
|
|
||||||
CONFIG = {
|
|
||||||
"verisure": {
|
|
||||||
"username": "test",
|
|
||||||
"password": "test",
|
|
||||||
"alarm": False,
|
|
||||||
"door_window": False,
|
|
||||||
"hygrometers": False,
|
|
||||||
"mouse": False,
|
|
||||||
"smartplugs": False,
|
|
||||||
"thermometers": False,
|
|
||||||
"smartcam": False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def mock_hub(config, response):
|
|
||||||
"""Extensively mock out a verisure hub."""
|
|
||||||
hub_prefix = "homeassistant.components.verisure.binary_sensor.hub"
|
|
||||||
verisure_prefix = "verisure.Session"
|
|
||||||
with patch(verisure_prefix) as session, patch(hub_prefix) as hub:
|
|
||||||
session.login.return_value = True
|
|
||||||
|
|
||||||
hub.config = config["verisure"]
|
|
||||||
hub.get.return_value = response
|
|
||||||
hub.get_first.return_value = response.get("ethernetConnectedNow", None)
|
|
||||||
|
|
||||||
yield hub
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_verisure(hass, config, response):
|
|
||||||
"""Set up mock verisure."""
|
|
||||||
with mock_hub(config, response):
|
|
||||||
await async_setup_component(hass, VERISURE_DOMAIN, config)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_verisure_no_ethernet_status(hass):
|
|
||||||
"""Test no data from API."""
|
|
||||||
await setup_verisure(hass, CONFIG, {})
|
|
||||||
assert len(hass.states.async_all()) == 1
|
|
||||||
entity_id = hass.states.async_entity_ids()[0]
|
|
||||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
|
||||||
|
|
||||||
|
|
||||||
async def test_verisure_ethernet_status_disconnected(hass):
|
|
||||||
"""Test disconnected."""
|
|
||||||
await setup_verisure(hass, CONFIG, {"ethernetConnectedNow": False})
|
|
||||||
assert len(hass.states.async_all()) == 1
|
|
||||||
entity_id = hass.states.async_entity_ids()[0]
|
|
||||||
assert hass.states.get(entity_id).state == "off"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_verisure_ethernet_status_connected(hass):
|
|
||||||
"""Test connected."""
|
|
||||||
await setup_verisure(hass, CONFIG, {"ethernetConnectedNow": True})
|
|
||||||
assert len(hass.states.async_all()) == 1
|
|
||||||
entity_id = hass.states.async_entity_ids()[0]
|
|
||||||
assert hass.states.get(entity_id).state == "on"
|
|
|
@ -1,144 +0,0 @@
|
||||||
"""Tests for the Verisure platform."""
|
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from unittest.mock import call, patch
|
|
||||||
|
|
||||||
from homeassistant.components.lock import (
|
|
||||||
DOMAIN as LOCK_DOMAIN,
|
|
||||||
SERVICE_LOCK,
|
|
||||||
SERVICE_UNLOCK,
|
|
||||||
)
|
|
||||||
from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN
|
|
||||||
from homeassistant.const import STATE_UNLOCKED
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
|
|
||||||
NO_DEFAULT_LOCK_CODE_CONFIG = {
|
|
||||||
"verisure": {
|
|
||||||
"username": "test",
|
|
||||||
"password": "test",
|
|
||||||
"locks": True,
|
|
||||||
"alarm": False,
|
|
||||||
"door_window": False,
|
|
||||||
"hygrometers": False,
|
|
||||||
"mouse": False,
|
|
||||||
"smartplugs": False,
|
|
||||||
"thermometers": False,
|
|
||||||
"smartcam": False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_LOCK_CODE_CONFIG = {
|
|
||||||
"verisure": {
|
|
||||||
"username": "test",
|
|
||||||
"password": "test",
|
|
||||||
"locks": True,
|
|
||||||
"default_lock_code": "9999",
|
|
||||||
"alarm": False,
|
|
||||||
"door_window": False,
|
|
||||||
"hygrometers": False,
|
|
||||||
"mouse": False,
|
|
||||||
"smartplugs": False,
|
|
||||||
"thermometers": False,
|
|
||||||
"smartcam": False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOCKS = ["door_lock"]
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def mock_hub(config, get_response=LOCKS[0]):
|
|
||||||
"""Extensively mock out a verisure hub."""
|
|
||||||
hub_prefix = "homeassistant.components.verisure.lock.hub"
|
|
||||||
# Since there is no conf to disable ethernet status, mock hub for
|
|
||||||
# binary sensor too
|
|
||||||
hub_binary_sensor = "homeassistant.components.verisure.binary_sensor.hub"
|
|
||||||
verisure_prefix = "verisure.Session"
|
|
||||||
with patch(verisure_prefix) as session, patch(hub_prefix) as hub:
|
|
||||||
session.login.return_value = True
|
|
||||||
|
|
||||||
hub.config = config["verisure"]
|
|
||||||
hub.get.return_value = LOCKS
|
|
||||||
hub.get_first.return_value = get_response.upper()
|
|
||||||
hub.session.set_lock_state.return_value = {
|
|
||||||
"doorLockStateChangeTransactionId": "test"
|
|
||||||
}
|
|
||||||
hub.session.get_lock_state_transaction.return_value = {"result": "OK"}
|
|
||||||
|
|
||||||
with patch(hub_binary_sensor, hub):
|
|
||||||
yield hub
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_verisure_locks(hass, config):
|
|
||||||
"""Set up mock verisure locks."""
|
|
||||||
with mock_hub(config):
|
|
||||||
await async_setup_component(hass, VERISURE_DOMAIN, config)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
# lock.door_lock, ethernet_status
|
|
||||||
assert len(hass.states.async_all()) == 2
|
|
||||||
|
|
||||||
|
|
||||||
async def test_verisure_no_default_code(hass):
|
|
||||||
"""Test configs without a default lock code."""
|
|
||||||
await setup_verisure_locks(hass, NO_DEFAULT_LOCK_CODE_CONFIG)
|
|
||||||
with mock_hub(NO_DEFAULT_LOCK_CODE_CONFIG, STATE_UNLOCKED) as hub:
|
|
||||||
|
|
||||||
mock = hub.session.set_lock_state
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock"}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_count == 0
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock", "code": "12345"}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_args == call("12345", LOCKS[0], "lock")
|
|
||||||
|
|
||||||
mock.reset_mock()
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_UNLOCK, {"entity_id": "lock.door_lock"}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_count == 0
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN,
|
|
||||||
SERVICE_UNLOCK,
|
|
||||||
{"entity_id": "lock.door_lock", "code": "12345"},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_args == call("12345", LOCKS[0], "unlock")
|
|
||||||
|
|
||||||
|
|
||||||
async def test_verisure_default_code(hass):
|
|
||||||
"""Test configs with a default lock code."""
|
|
||||||
await setup_verisure_locks(hass, DEFAULT_LOCK_CODE_CONFIG)
|
|
||||||
with mock_hub(DEFAULT_LOCK_CODE_CONFIG, STATE_UNLOCKED) as hub:
|
|
||||||
mock = hub.session.set_lock_state
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock"}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_args == call("9999", LOCKS[0], "lock")
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_UNLOCK, {"entity_id": "lock.door_lock"}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_args == call("9999", LOCKS[0], "unlock")
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock", "code": "12345"}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_args == call("12345", LOCKS[0], "lock")
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
LOCK_DOMAIN,
|
|
||||||
SERVICE_UNLOCK,
|
|
||||||
{"entity_id": "lock.door_lock", "code": "12345"},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert mock.call_args == call("12345", LOCKS[0], "unlock")
|
|
Loading…
Add table
Add a link
Reference in a new issue