mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
Update Verisure package to 2.6.1 (#89318)
Co-authored-by: Franck Nijhof <frenck@frenck.nl> Co-authored-by: RobinBolder <33325401+RobinBolder@users.noreply.github.com> Co-authored-by: Tobias Lindaaker <tobias@thobe.org>
This commit is contained in:
parent
6e92dac61f
commit
1baadc1d09
@ -1293,8 +1293,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/velux/ @Julius2342
|
/homeassistant/components/velux/ @Julius2342
|
||||||
/homeassistant/components/venstar/ @garbled1
|
/homeassistant/components/venstar/ @garbled1
|
||||||
/tests/components/venstar/ @garbled1
|
/tests/components/venstar/ @garbled1
|
||||||
/homeassistant/components/verisure/ @frenck
|
/homeassistant/components/verisure/ @frenck @niro1987
|
||||||
/tests/components/verisure/ @frenck
|
/tests/components/verisure/ @frenck @niro1987
|
||||||
/homeassistant/components/versasense/ @flamm3blemuff1n
|
/homeassistant/components/versasense/ @flamm3blemuff1n
|
||||||
/homeassistant/components/version/ @ludeeus
|
/homeassistant/components/version/ @ludeeus
|
||||||
/tests/components/version/ @ludeeus
|
/tests/components/version/ @ludeeus
|
||||||
|
@ -6,9 +6,9 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_EMAIL, EVENT_HOMEASSISTANT_STOP, Platform
|
from homeassistant.const import CONF_EMAIL, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.storage import STORAGE_DIR
|
from homeassistant.helpers.storage import STORAGE_DIR
|
||||||
|
|
||||||
@ -34,11 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
coordinator = VerisureDataUpdateCoordinator(hass, entry=entry)
|
coordinator = VerisureDataUpdateCoordinator(hass, entry=entry)
|
||||||
|
|
||||||
if not await coordinator.async_login():
|
if not await coordinator.async_login():
|
||||||
raise ConfigEntryAuthFailed
|
raise ConfigEntryNotReady("Could not log in to verisure.")
|
||||||
|
|
||||||
entry.async_on_unload(
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout)
|
|
||||||
)
|
|
||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
@ -55,33 +55,49 @@ class VerisureAlarm(
|
|||||||
"""Return the unique ID for this entity."""
|
"""Return the unique ID for this entity."""
|
||||||
return self.coordinator.entry.data[CONF_GIID]
|
return self.coordinator.entry.data[CONF_GIID]
|
||||||
|
|
||||||
async def _async_set_arm_state(self, state: str, code: str | None = None) -> None:
|
async def _async_set_arm_state(
|
||||||
|
self, state: str, command_data: dict[str, str | dict[str, str]]
|
||||||
|
) -> None:
|
||||||
"""Send set arm state command."""
|
"""Send set arm state command."""
|
||||||
arm_state = await self.hass.async_add_executor_job(
|
arm_state = await self.hass.async_add_executor_job(
|
||||||
self.coordinator.verisure.set_arm_state, code, state
|
self.coordinator.verisure.request, command_data
|
||||||
)
|
)
|
||||||
LOGGER.debug("Verisure set arm state %s", state)
|
LOGGER.debug("Verisure set arm state %s", state)
|
||||||
transaction = {}
|
result = None
|
||||||
while "result" not in transaction:
|
while result is None:
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
transaction = await self.hass.async_add_executor_job(
|
transaction = await self.hass.async_add_executor_job(
|
||||||
self.coordinator.verisure.get_arm_state_transaction,
|
self.coordinator.verisure.request,
|
||||||
arm_state["armStateChangeTransactionId"],
|
self.coordinator.verisure.poll_arm_state(
|
||||||
|
list(arm_state["data"].values())[0], state
|
||||||
|
),
|
||||||
|
)
|
||||||
|
result = (
|
||||||
|
transaction.get("data", {})
|
||||||
|
.get("installation", {})
|
||||||
|
.get("armStateChangePollResult", {})
|
||||||
|
.get("result")
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.coordinator.async_refresh()
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
await self._async_set_arm_state("DISARMED", code)
|
await self._async_set_arm_state(
|
||||||
|
"DISARMED", self.coordinator.verisure.disarm(code)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_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."""
|
||||||
await self._async_set_arm_state("ARMED_HOME", code)
|
await self._async_set_arm_state(
|
||||||
|
"ARMED_HOME", self.coordinator.verisure.arm_home(code)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_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."""
|
||||||
await self._async_set_arm_state("ARMED_AWAY", code)
|
await self._async_set_arm_state(
|
||||||
|
"ARMED_AWAY", self.coordinator.verisure.arm_away(code)
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
@ -109,9 +109,9 @@ class VerisureEthernetStatus(
|
|||||||
@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 self.coordinator.data["ethernet"]
|
return self.coordinator.data["broadband"]["isBroadbandConnected"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return super().available and self.coordinator.data["ethernet"] is not None
|
return super().available and self.coordinator.data["broadband"] is not None
|
||||||
|
@ -63,12 +63,12 @@ class VerisureSmartcam(CoordinatorEntity[VerisureDataUpdateCoordinator], Camera)
|
|||||||
self.serial_number = serial_number
|
self.serial_number = serial_number
|
||||||
self._directory_path = directory_path
|
self._directory_path = directory_path
|
||||||
self._image: str | None = None
|
self._image: str | None = None
|
||||||
self._image_id = None
|
self._image_id: str | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device information about this entity."""
|
"""Return device information about this entity."""
|
||||||
area = self.coordinator.data["cameras"][self.serial_number]["area"]
|
area = self.coordinator.data["cameras"][self.serial_number]["device"]["area"]
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
name=area,
|
name=area,
|
||||||
suggested_area=area,
|
suggested_area=area,
|
||||||
@ -95,16 +95,16 @@ class VerisureSmartcam(CoordinatorEntity[VerisureDataUpdateCoordinator], Camera)
|
|||||||
"""Check the contents of the image list."""
|
"""Check the contents of the image list."""
|
||||||
self.coordinator.update_smartcam_imageseries()
|
self.coordinator.update_smartcam_imageseries()
|
||||||
|
|
||||||
images = self.coordinator.imageseries.get("imageSeries", [])
|
new_image = None
|
||||||
new_image_id = None
|
for image in self.coordinator.imageseries:
|
||||||
for image in images:
|
|
||||||
if image["deviceLabel"] == self.serial_number:
|
if image["deviceLabel"] == self.serial_number:
|
||||||
new_image_id = image["image"][0]["imageId"]
|
new_image = image
|
||||||
break
|
break
|
||||||
|
|
||||||
if not new_image_id:
|
if not new_image:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
new_image_id = new_image["mediaId"]
|
||||||
if new_image_id in ("-1", self._image_id):
|
if new_image_id in ("-1", self._image_id):
|
||||||
LOGGER.debug("The image is the same, or loading image_id")
|
LOGGER.debug("The image is the same, or loading image_id")
|
||||||
return
|
return
|
||||||
@ -113,9 +113,8 @@ class VerisureSmartcam(CoordinatorEntity[VerisureDataUpdateCoordinator], 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")
|
||||||
)
|
)
|
||||||
self.coordinator.verisure.download_image(
|
new_image_url = new_image["contentUrl"]
|
||||||
self.serial_number, new_image_id, new_image_path
|
self.coordinator.verisure.download_image(new_image_url, 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()
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
self.verisure = Verisure(
|
self.verisure = Verisure(
|
||||||
username=self.email,
|
username=self.email,
|
||||||
password=self.password,
|
password=self.password,
|
||||||
cookieFileName=self.hass.config.path(
|
cookie_file_name=self.hass.config.path(
|
||||||
STORAGE_DIR, f"verisure_{user_input[CONF_EMAIL]}"
|
STORAGE_DIR, f"verisure_{user_input[CONF_EMAIL]}"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -66,7 +66,9 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
except VerisureLoginError as ex:
|
except VerisureLoginError as ex:
|
||||||
if "Multifactor authentication enabled" in str(ex):
|
if "Multifactor authentication enabled" in str(ex):
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(self.verisure.login_mfa)
|
await self.hass.async_add_executor_job(
|
||||||
|
self.verisure.request_mfa
|
||||||
|
)
|
||||||
except (
|
except (
|
||||||
VerisureLoginError,
|
VerisureLoginError,
|
||||||
VerisureError,
|
VerisureError,
|
||||||
@ -108,9 +110,8 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(
|
||||||
self.verisure.mfa_validate, user_input[CONF_CODE], True
|
self.verisure.validate_mfa, user_input[CONF_CODE]
|
||||||
)
|
)
|
||||||
await self.hass.async_add_executor_job(self.verisure.login)
|
|
||||||
except VerisureLoginError as ex:
|
except VerisureLoginError as ex:
|
||||||
LOGGER.debug("Could not log in to Verisure, %s", ex)
|
LOGGER.debug("Could not log in to Verisure, %s", ex)
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
@ -136,9 +137,16 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Select Verisure installation to add."""
|
"""Select Verisure installation to add."""
|
||||||
|
installations_data = await self.hass.async_add_executor_job(
|
||||||
|
self.verisure.get_installations
|
||||||
|
)
|
||||||
installations = {
|
installations = {
|
||||||
inst["giid"]: f"{inst['alias']} ({inst['street']})"
|
inst["giid"]: f"{inst['alias']} ({inst['address']['street']})"
|
||||||
for inst in self.verisure.installations or []
|
for inst in (
|
||||||
|
installations_data.get("data", {})
|
||||||
|
.get("account", {})
|
||||||
|
.get("installations", [])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
@ -184,8 +192,8 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
self.verisure = Verisure(
|
self.verisure = Verisure(
|
||||||
username=self.email,
|
username=self.email,
|
||||||
password=self.password,
|
password=self.password,
|
||||||
cookieFileName=self.hass.config.path(
|
cookie_file_name=self.hass.config.path(
|
||||||
STORAGE_DIR, f"verisure-{user_input[CONF_EMAIL]}"
|
STORAGE_DIR, f"verisure_{user_input[CONF_EMAIL]}"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -194,7 +202,9 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
except VerisureLoginError as ex:
|
except VerisureLoginError as ex:
|
||||||
if "Multifactor authentication enabled" in str(ex):
|
if "Multifactor authentication enabled" in str(ex):
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(self.verisure.login_mfa)
|
await self.hass.async_add_executor_job(
|
||||||
|
self.verisure.request_mfa
|
||||||
|
)
|
||||||
except (
|
except (
|
||||||
VerisureLoginError,
|
VerisureLoginError,
|
||||||
VerisureError,
|
VerisureError,
|
||||||
@ -248,7 +258,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(
|
||||||
self.verisure.mfa_validate, user_input[CONF_CODE], True
|
self.verisure.validate_mfa, user_input[CONF_CODE]
|
||||||
)
|
)
|
||||||
await self.hass.async_add_executor_job(self.verisure.login)
|
await self.hass.async_add_executor_job(self.verisure.login)
|
||||||
except VerisureLoginError as ex:
|
except VerisureLoginError as ex:
|
||||||
|
@ -36,6 +36,9 @@ DEVICE_TYPE_NAME = {
|
|||||||
"SMOKE3": "Smoke detector",
|
"SMOKE3": "Smoke detector",
|
||||||
"VOICEBOX1": "VoiceBox",
|
"VOICEBOX1": "VoiceBox",
|
||||||
"WATER1": "Water detector",
|
"WATER1": "Water detector",
|
||||||
|
"SMOKE": "Smoke detector",
|
||||||
|
"SIREN": "Siren",
|
||||||
|
"VOICEBOX": "VoiceBox",
|
||||||
}
|
}
|
||||||
|
|
||||||
ALARM_STATE_TO_HA = {
|
ALARM_STATE_TO_HA = {
|
||||||
|
@ -2,19 +2,21 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from http import HTTPStatus
|
from time import sleep
|
||||||
|
|
||||||
from verisure import (
|
from verisure import (
|
||||||
Error as VerisureError,
|
Error as VerisureError,
|
||||||
|
LoginError as VerisureLoginError,
|
||||||
ResponseError as VerisureResponseError,
|
ResponseError as VerisureResponseError,
|
||||||
Session as Verisure,
|
Session as Verisure,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.storage import STORAGE_DIR
|
from homeassistant.helpers.storage import STORAGE_DIR
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import CONF_GIID, DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER
|
from .const import CONF_GIID, DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER
|
||||||
@ -25,13 +27,14 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Initialize the Verisure hub."""
|
"""Initialize the Verisure hub."""
|
||||||
self.imageseries: dict[str, list] = {}
|
self.imageseries: list[dict[str, str]] = []
|
||||||
self.entry = entry
|
self.entry = entry
|
||||||
|
self._overview: list[dict] = []
|
||||||
|
|
||||||
self.verisure = Verisure(
|
self.verisure = Verisure(
|
||||||
username=entry.data[CONF_EMAIL],
|
username=entry.data[CONF_EMAIL],
|
||||||
password=entry.data[CONF_PASSWORD],
|
password=entry.data[CONF_PASSWORD],
|
||||||
cookieFileName=hass.config.path(
|
cookie_file_name=hass.config.path(
|
||||||
STORAGE_DIR, f"verisure_{entry.data[CONF_EMAIL]}"
|
STORAGE_DIR, f"verisure_{entry.data[CONF_EMAIL]}"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -43,8 +46,11 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
async def async_login(self) -> bool:
|
async def async_login(self) -> bool:
|
||||||
"""Login to Verisure."""
|
"""Login to Verisure."""
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(self.verisure.login)
|
await self.hass.async_add_executor_job(self.verisure.login_cookie)
|
||||||
except VerisureError as ex:
|
except VerisureLoginError as ex:
|
||||||
|
LOGGER.error("Could not log in to verisure, %s", ex)
|
||||||
|
raise ConfigEntryAuthFailed("Credentials expired for Verisure") from ex
|
||||||
|
except VerisureResponseError 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
|
||||||
|
|
||||||
@ -54,62 +60,116 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def async_logout(self, _event: Event) -> None:
|
|
||||||
"""Logout from Verisure."""
|
|
||||||
try:
|
|
||||||
await self.hass.async_add_executor_job(self.verisure.logout)
|
|
||||||
except VerisureError as ex:
|
|
||||||
LOGGER.error("Could not log out from verisure, %s", ex)
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict:
|
async def _async_update_data(self) -> dict:
|
||||||
"""Fetch data from Verisure."""
|
"""Fetch data from Verisure."""
|
||||||
try:
|
try:
|
||||||
overview = await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(self.verisure.update_cookie)
|
||||||
self.verisure.get_overview
|
except VerisureLoginError as ex:
|
||||||
)
|
LOGGER.error("Credentials expired for Verisure, %s", ex)
|
||||||
|
raise ConfigEntryAuthFailed("Credentials expired for Verisure") from ex
|
||||||
except VerisureResponseError as ex:
|
except VerisureResponseError as ex:
|
||||||
LOGGER.error("Could not read overview, %s", ex)
|
LOGGER.error("Could not log in to verisure, %s", ex)
|
||||||
if ex.status_code == HTTPStatus.SERVICE_UNAVAILABLE:
|
raise ConfigEntryAuthFailed("Could not log in to verisure") from ex
|
||||||
LOGGER.info("Trying to log in again")
|
try:
|
||||||
await self.async_login()
|
overview = await self.hass.async_add_executor_job(
|
||||||
return {}
|
self.verisure.request,
|
||||||
raise
|
self.verisure.arm_state(),
|
||||||
|
self.verisure.broadband(),
|
||||||
|
self.verisure.cameras(),
|
||||||
|
self.verisure.climate(),
|
||||||
|
self.verisure.door_window(),
|
||||||
|
self.verisure.smart_lock(),
|
||||||
|
self.verisure.smartplugs(),
|
||||||
|
)
|
||||||
|
except VerisureResponseError as err:
|
||||||
|
LOGGER.debug("Cookie expired or service unavailable, %s", err)
|
||||||
|
overview = self._overview
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(self.verisure.update_cookie)
|
||||||
|
except VerisureResponseError as ex:
|
||||||
|
raise ConfigEntryAuthFailed("Credentials for Verisure expired.") from ex
|
||||||
|
except VerisureError as err:
|
||||||
|
LOGGER.error("Could not read overview, %s", err)
|
||||||
|
raise UpdateFailed("Could not read overview") from err
|
||||||
|
|
||||||
|
def unpack(overview: list, value: str) -> dict | list:
|
||||||
|
return next(
|
||||||
|
(
|
||||||
|
item["data"]["installation"][value]
|
||||||
|
for item in overview
|
||||||
|
if value in item.get("data", {}).get("installation", {})
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
# Store data in a way Home Assistant can easily consume it
|
# Store data in a way Home Assistant can easily consume it
|
||||||
|
self._overview = overview
|
||||||
return {
|
return {
|
||||||
"alarm": overview["armState"],
|
"alarm": unpack(overview, "armState"),
|
||||||
"ethernet": overview.get("ethernetConnectedNow"),
|
"broadband": unpack(overview, "broadband"),
|
||||||
"cameras": {
|
"cameras": {
|
||||||
device["deviceLabel"]: device
|
device["device"]["deviceLabel"]: device
|
||||||
for device in overview["customerImageCameras"]
|
for device in unpack(overview, "cameras")
|
||||||
},
|
},
|
||||||
"climate": {
|
"climate": {
|
||||||
device["deviceLabel"]: device for device in overview["climateValues"]
|
device["device"]["deviceLabel"]: device
|
||||||
|
for device in unpack(overview, "climates")
|
||||||
},
|
},
|
||||||
"door_window": {
|
"door_window": {
|
||||||
device["deviceLabel"]: device
|
device["device"]["deviceLabel"]: device
|
||||||
for device in overview["doorWindow"]["doorWindowDevice"]
|
for device in unpack(overview, "doorWindows")
|
||||||
},
|
},
|
||||||
"locks": {
|
"locks": {
|
||||||
device["deviceLabel"]: device
|
device["device"]["deviceLabel"]: device
|
||||||
for device in overview["doorLockStatusList"]
|
for device in unpack(overview, "smartLocks")
|
||||||
},
|
|
||||||
"mice": {
|
|
||||||
device["deviceLabel"]: device
|
|
||||||
for device in overview["eventCounts"]
|
|
||||||
if device["deviceType"] == "MOUSE1"
|
|
||||||
},
|
},
|
||||||
"smart_plugs": {
|
"smart_plugs": {
|
||||||
device["deviceLabel"]: device for device in overview["smartPlugs"]
|
device["device"]["deviceLabel"]: device
|
||||||
|
for device in unpack(overview, "smartplugs")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throttle(timedelta(seconds=60))
|
@Throttle(timedelta(seconds=60))
|
||||||
def update_smartcam_imageseries(self) -> None:
|
def update_smartcam_imageseries(self) -> None:
|
||||||
"""Update the image series."""
|
"""Update the image series."""
|
||||||
self.imageseries = self.verisure.get_camera_imageseries()
|
image_data = self.verisure.request(self.verisure.cameras_image_series())
|
||||||
|
self.imageseries = [
|
||||||
|
content
|
||||||
|
for series in (
|
||||||
|
image_data.get("data", {})
|
||||||
|
.get("ContentProviderMediaSearch", {})
|
||||||
|
.get("mediaSeriesList", [])
|
||||||
|
)
|
||||||
|
for content in series.get("deviceMediaList", [])
|
||||||
|
if content.get("contentType") == "IMAGE_JPEG"
|
||||||
|
]
|
||||||
|
|
||||||
@Throttle(timedelta(seconds=30))
|
@Throttle(timedelta(seconds=30))
|
||||||
def smartcam_capture(self, device_id: str) -> None:
|
def smartcam_capture(self, device_id: str) -> None:
|
||||||
"""Capture a new image from a smartcam."""
|
"""Capture a new image from a smartcam."""
|
||||||
self.verisure.capture_image(device_id)
|
capture_request = self.verisure.request(
|
||||||
|
self.verisure.camera_get_request_id(device_id)
|
||||||
|
)
|
||||||
|
request_id = (
|
||||||
|
capture_request.get("data", {})
|
||||||
|
.get("ContentProviderCaptureImageRequest", {})
|
||||||
|
.get("requestId")
|
||||||
|
)
|
||||||
|
capture_status = None
|
||||||
|
attempts = 0
|
||||||
|
while capture_status != "AVAILABLE":
|
||||||
|
if attempts == 30:
|
||||||
|
break
|
||||||
|
if attempts > 1:
|
||||||
|
sleep(0.5)
|
||||||
|
attempts += 1
|
||||||
|
capture_data = self.verisure.request(
|
||||||
|
self.verisure.camera_capture(device_id, request_id)
|
||||||
|
)
|
||||||
|
capture_status = (
|
||||||
|
capture_data.get("data", {})
|
||||||
|
.get("installation", {})
|
||||||
|
.get("cameraContentProvider", {})
|
||||||
|
.get("captureImageRequestStatus", {})
|
||||||
|
.get("mediaRequestStatus")
|
||||||
|
)
|
||||||
|
@ -16,6 +16,7 @@ TO_REDACT = {
|
|||||||
"deviceArea",
|
"deviceArea",
|
||||||
"name",
|
"name",
|
||||||
"time",
|
"time",
|
||||||
|
"reportTime",
|
||||||
"userString",
|
"userString",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ class VerisureDoorlock(CoordinatorEntity[VerisureDataUpdateCoordinator], LockEnt
|
|||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device information about this entity."""
|
"""Return device information about this entity."""
|
||||||
area = self.coordinator.data["locks"][self.serial_number]["area"]
|
area = self.coordinator.data["locks"][self.serial_number]["device"]["area"]
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
name=area,
|
name=area,
|
||||||
suggested_area=area,
|
suggested_area=area,
|
||||||
@ -98,12 +98,16 @@ class VerisureDoorlock(CoordinatorEntity[VerisureDataUpdateCoordinator], LockEnt
|
|||||||
@property
|
@property
|
||||||
def changed_by(self) -> str | None:
|
def changed_by(self) -> str | None:
|
||||||
"""Last change triggered by."""
|
"""Last change triggered by."""
|
||||||
return self.coordinator.data["locks"][self.serial_number].get("userString")
|
return (
|
||||||
|
self.coordinator.data["locks"][self.serial_number]
|
||||||
|
.get("user", {})
|
||||||
|
.get("name")
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def changed_method(self) -> str:
|
def changed_method(self) -> str:
|
||||||
"""Last change method."""
|
"""Last change method."""
|
||||||
return self.coordinator.data["locks"][self.serial_number]["method"]
|
return self.coordinator.data["locks"][self.serial_number]["lockMethod"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def code_format(self) -> str:
|
def code_format(self) -> str:
|
||||||
@ -114,8 +118,7 @@ class VerisureDoorlock(CoordinatorEntity[VerisureDataUpdateCoordinator], LockEnt
|
|||||||
def is_locked(self) -> bool:
|
def is_locked(self) -> bool:
|
||||||
"""Return true if lock is locked."""
|
"""Return true if lock is locked."""
|
||||||
return (
|
return (
|
||||||
self.coordinator.data["locks"][self.serial_number]["lockedState"]
|
self.coordinator.data["locks"][self.serial_number]["lockStatus"] == "LOCKED"
|
||||||
== "LOCKED"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -147,28 +150,39 @@ class VerisureDoorlock(CoordinatorEntity[VerisureDataUpdateCoordinator], LockEnt
|
|||||||
|
|
||||||
async def async_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."""
|
||||||
target_state = "lock" if state == STATE_LOCKED else "unlock"
|
command = (
|
||||||
lock_state = await self.hass.async_add_executor_job(
|
self.coordinator.verisure.door_lock(self.serial_number, code)
|
||||||
self.coordinator.verisure.set_lock_state,
|
if state == STATE_LOCKED
|
||||||
code,
|
else self.coordinator.verisure.door_unlock(self.serial_number, code)
|
||||||
self.serial_number,
|
)
|
||||||
target_state,
|
lock_request = await self.hass.async_add_executor_job(
|
||||||
|
self.coordinator.verisure.request,
|
||||||
|
command,
|
||||||
)
|
)
|
||||||
|
|
||||||
LOGGER.debug("Verisure doorlock %s", state)
|
LOGGER.debug("Verisure doorlock %s", state)
|
||||||
transaction = {}
|
transaction_id = lock_request.get("data", {}).get(command["operationName"])
|
||||||
|
target_state = "LOCKED" if state == STATE_LOCKED else "UNLOCKED"
|
||||||
|
lock_status = None
|
||||||
attempts = 0
|
attempts = 0
|
||||||
while "result" not in transaction:
|
while lock_status != "OK":
|
||||||
transaction = await self.hass.async_add_executor_job(
|
|
||||||
self.coordinator.verisure.get_lock_state_transaction,
|
|
||||||
lock_state["doorLockStateChangeTransactionId"],
|
|
||||||
)
|
|
||||||
attempts += 1
|
|
||||||
if attempts == 30:
|
if attempts == 30:
|
||||||
break
|
break
|
||||||
if attempts > 1:
|
if attempts > 1:
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
if transaction["result"] == "OK":
|
attempts += 1
|
||||||
|
poll_data = await self.hass.async_add_executor_job(
|
||||||
|
self.coordinator.verisure.request,
|
||||||
|
self.coordinator.verisure.poll_lock_state(
|
||||||
|
transaction_id, self.serial_number, target_state
|
||||||
|
),
|
||||||
|
)
|
||||||
|
lock_status = (
|
||||||
|
poll_data.get("data", {})
|
||||||
|
.get("installation", {})
|
||||||
|
.get("doorLockStateChangePollResult", {})
|
||||||
|
.get("result")
|
||||||
|
)
|
||||||
|
if lock_status == "OK":
|
||||||
self._state = state
|
self._state = state
|
||||||
|
|
||||||
def disable_autolock(self) -> None:
|
def disable_autolock(self) -> None:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "verisure",
|
"domain": "verisure",
|
||||||
"name": "Verisure",
|
"name": "Verisure",
|
||||||
"codeowners": ["@frenck"],
|
"codeowners": ["@frenck", "@niro1987"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
{
|
{
|
||||||
@ -12,5 +12,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["verisure"],
|
"loggers": ["verisure"],
|
||||||
"requirements": ["vsure==1.8.1"]
|
"requirements": ["vsure==2.6.1"]
|
||||||
}
|
}
|
||||||
|
@ -28,18 +28,13 @@ async def async_setup_entry(
|
|||||||
sensors: list[Entity] = [
|
sensors: list[Entity] = [
|
||||||
VerisureThermometer(coordinator, serial_number)
|
VerisureThermometer(coordinator, serial_number)
|
||||||
for serial_number, values in coordinator.data["climate"].items()
|
for serial_number, values in coordinator.data["climate"].items()
|
||||||
if "temperature" in values
|
if "temperatureValue" in values
|
||||||
]
|
]
|
||||||
|
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
VerisureHygrometer(coordinator, serial_number)
|
VerisureHygrometer(coordinator, serial_number)
|
||||||
for serial_number, values in coordinator.data["climate"].items()
|
for serial_number, values in coordinator.data["climate"].items()
|
||||||
if "humidity" in values
|
if values.get("humidityEnabled")
|
||||||
)
|
|
||||||
|
|
||||||
sensors.extend(
|
|
||||||
VerisureMouseDetection(coordinator, serial_number)
|
|
||||||
for serial_number in coordinator.data["mice"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(sensors)
|
async_add_entities(sensors)
|
||||||
@ -67,10 +62,10 @@ class VerisureThermometer(
|
|||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device information about this entity."""
|
"""Return device information about this entity."""
|
||||||
device_type = self.coordinator.data["climate"][self.serial_number].get(
|
device_type = self.coordinator.data["climate"][self.serial_number]["device"][
|
||||||
"deviceType"
|
"gui"
|
||||||
)
|
]["label"]
|
||||||
area = self.coordinator.data["climate"][self.serial_number]["deviceArea"]
|
area = self.coordinator.data["climate"][self.serial_number]["device"]["area"]
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
name=area,
|
name=area,
|
||||||
suggested_area=area,
|
suggested_area=area,
|
||||||
@ -84,7 +79,7 @@ class VerisureThermometer(
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> str | None:
|
def native_value(self) -> str | None:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
return self.coordinator.data["climate"][self.serial_number]["temperature"]
|
return self.coordinator.data["climate"][self.serial_number]["temperatureValue"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
@ -92,7 +87,8 @@ class VerisureThermometer(
|
|||||||
return (
|
return (
|
||||||
super().available
|
super().available
|
||||||
and self.serial_number in self.coordinator.data["climate"]
|
and self.serial_number in self.coordinator.data["climate"]
|
||||||
and "temperature" in self.coordinator.data["climate"][self.serial_number]
|
and "temperatureValue"
|
||||||
|
in self.coordinator.data["climate"][self.serial_number]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -118,10 +114,10 @@ class VerisureHygrometer(
|
|||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device information about this entity."""
|
"""Return device information about this entity."""
|
||||||
device_type = self.coordinator.data["climate"][self.serial_number].get(
|
device_type = self.coordinator.data["climate"][self.serial_number]["device"][
|
||||||
"deviceType"
|
"gui"
|
||||||
)
|
]["label"]
|
||||||
area = self.coordinator.data["climate"][self.serial_number]["deviceArea"]
|
area = self.coordinator.data["climate"][self.serial_number]["device"]["area"]
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
name=area,
|
name=area,
|
||||||
suggested_area=area,
|
suggested_area=area,
|
||||||
@ -135,7 +131,7 @@ class VerisureHygrometer(
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> str | None:
|
def native_value(self) -> str | None:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
return self.coordinator.data["climate"][self.serial_number]["humidity"]
|
return self.coordinator.data["climate"][self.serial_number]["humidityValue"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
@ -143,51 +139,5 @@ class VerisureHygrometer(
|
|||||||
return (
|
return (
|
||||||
super().available
|
super().available
|
||||||
and self.serial_number in self.coordinator.data["climate"]
|
and self.serial_number in self.coordinator.data["climate"]
|
||||||
and "humidity" in self.coordinator.data["climate"][self.serial_number]
|
and "humidityValue" in self.coordinator.data["climate"][self.serial_number]
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VerisureMouseDetection(
|
|
||||||
CoordinatorEntity[VerisureDataUpdateCoordinator], SensorEntity
|
|
||||||
):
|
|
||||||
"""Representation of a Verisure mouse detector."""
|
|
||||||
|
|
||||||
_attr_name = "Mouse"
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
_attr_native_unit_of_measurement = "Mice"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, coordinator: VerisureDataUpdateCoordinator, serial_number: str
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the sensor."""
|
|
||||||
super().__init__(coordinator)
|
|
||||||
self._attr_unique_id = f"{serial_number}_mice"
|
|
||||||
self.serial_number = serial_number
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return device information about this entity."""
|
|
||||||
area = self.coordinator.data["mice"][self.serial_number]["area"]
|
|
||||||
return DeviceInfo(
|
|
||||||
name=area,
|
|
||||||
suggested_area=area,
|
|
||||||
manufacturer="Verisure",
|
|
||||||
model="Mouse detector",
|
|
||||||
identifiers={(DOMAIN, self.serial_number)},
|
|
||||||
via_device=(DOMAIN, self.coordinator.entry.data[CONF_GIID]),
|
|
||||||
configuration_url="https://mypages.verisure.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self) -> str | None:
|
|
||||||
"""Return the state of the entity."""
|
|
||||||
return self.coordinator.data["mice"][self.serial_number]["detections"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return True if entity is available."""
|
|
||||||
return (
|
|
||||||
super().available
|
|
||||||
and self.serial_number in self.coordinator.data["mice"]
|
|
||||||
and "detections" in self.coordinator.data["mice"][self.serial_number]
|
|
||||||
)
|
)
|
||||||
|
@ -47,7 +47,9 @@ class VerisureSmartplug(CoordinatorEntity[VerisureDataUpdateCoordinator], Switch
|
|||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return device information about this entity."""
|
"""Return device information about this entity."""
|
||||||
area = self.coordinator.data["smart_plugs"][self.serial_number]["area"]
|
area = self.coordinator.data["smart_plugs"][self.serial_number]["device"][
|
||||||
|
"area"
|
||||||
|
]
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
name=area,
|
name=area,
|
||||||
suggested_area=area,
|
suggested_area=area,
|
||||||
@ -77,16 +79,23 @@ class VerisureSmartplug(CoordinatorEntity[VerisureDataUpdateCoordinator], Switch
|
|||||||
and self.serial_number in self.coordinator.data["smart_plugs"]
|
and self.serial_number in self.coordinator.data["smart_plugs"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Set smartplug status on."""
|
"""Turn the smartplug on."""
|
||||||
self.coordinator.verisure.set_smartplug_state(self.serial_number, True)
|
await self.async_set_plug_state(True)
|
||||||
self._state = True
|
|
||||||
self._change_timestamp = monotonic()
|
|
||||||
self.schedule_update_ha_state()
|
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Set smartplug status off."""
|
"""Turn the smartplug off."""
|
||||||
self.coordinator.verisure.set_smartplug_state(self.serial_number, False)
|
await self.async_set_plug_state(False)
|
||||||
self._state = False
|
|
||||||
|
async def async_set_plug_state(self, state: bool) -> None:
|
||||||
|
"""Set smartplug state."""
|
||||||
|
command: dict[
|
||||||
|
str, str | dict[str, str]
|
||||||
|
] = self.coordinator.verisure.set_smartplug(self.serial_number, state)
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.coordinator.verisure.request,
|
||||||
|
command,
|
||||||
|
)
|
||||||
|
self._state = state
|
||||||
self._change_timestamp = monotonic()
|
self._change_timestamp = monotonic()
|
||||||
self.schedule_update_ha_state()
|
await self.coordinator.async_request_refresh()
|
||||||
|
@ -2589,7 +2589,7 @@ volkszaehler==0.4.0
|
|||||||
volvooncall==0.10.2
|
volvooncall==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.verisure
|
# homeassistant.components.verisure
|
||||||
vsure==1.8.1
|
vsure==2.6.1
|
||||||
|
|
||||||
# homeassistant.components.vasttrafik
|
# homeassistant.components.vasttrafik
|
||||||
vtjp==0.1.14
|
vtjp==0.1.14
|
||||||
|
@ -1847,7 +1847,7 @@ vilfo-api-client==0.3.2
|
|||||||
volvooncall==0.10.2
|
volvooncall==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.verisure
|
# homeassistant.components.verisure
|
||||||
vsure==1.8.1
|
vsure==2.6.1
|
||||||
|
|
||||||
# homeassistant.components.vulcan
|
# homeassistant.components.vulcan
|
||||||
vulcan-api==2.3.0
|
vulcan-api==2.3.0
|
||||||
|
@ -43,8 +43,22 @@ def mock_verisure_config_flow() -> Generator[None, MagicMock, None]:
|
|||||||
) as verisure_mock:
|
) as verisure_mock:
|
||||||
verisure = verisure_mock.return_value
|
verisure = verisure_mock.return_value
|
||||||
verisure.login.return_value = True
|
verisure.login.return_value = True
|
||||||
verisure.installations = [
|
verisure.get_installations.return_value = {
|
||||||
{"giid": "12345", "alias": "ascending", "street": "12345th street"},
|
"data": {
|
||||||
{"giid": "54321", "alias": "descending", "street": "54321th street"},
|
"account": {
|
||||||
]
|
"installations": [
|
||||||
|
{
|
||||||
|
"giid": "12345",
|
||||||
|
"alias": "ascending",
|
||||||
|
"address": {"street": "12345th street"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"giid": "54321",
|
||||||
|
"alias": "descending",
|
||||||
|
"address": {"street": "54321th street"},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
yield verisure
|
yield verisure
|
||||||
|
@ -35,9 +35,10 @@ async def test_full_user_flow_single_installation(
|
|||||||
assert result.get("type") == FlowResultType.FORM
|
assert result.get("type") == FlowResultType.FORM
|
||||||
assert result.get("errors") == {}
|
assert result.get("errors") == {}
|
||||||
|
|
||||||
mock_verisure_config_flow.installations = [
|
mock_verisure_config_flow.get_installations.return_value = {
|
||||||
mock_verisure_config_flow.installations[0]
|
k1: {k2: {k3: [v3[0]] for k3, v3 in v2.items()} for k2, v2 in v1.items()}
|
||||||
]
|
for k1, v1 in mock_verisure_config_flow.get_installations.return_value.items()
|
||||||
|
}
|
||||||
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -133,9 +134,10 @@ async def test_full_user_flow_single_installation_with_mfa(
|
|||||||
assert result2.get("step_id") == "mfa"
|
assert result2.get("step_id") == "mfa"
|
||||||
|
|
||||||
mock_verisure_config_flow.login.side_effect = None
|
mock_verisure_config_flow.login.side_effect = None
|
||||||
mock_verisure_config_flow.installations = [
|
mock_verisure_config_flow.get_installations.return_value = {
|
||||||
mock_verisure_config_flow.installations[0]
|
k1: {k2: {k3: [v3[0]] for k3, v3 in v2.items()} for k2, v2 in v1.items()}
|
||||||
]
|
for k1, v1 in mock_verisure_config_flow.get_installations.return_value.items()
|
||||||
|
}
|
||||||
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -153,9 +155,9 @@ async def test_full_user_flow_single_installation_with_mfa(
|
|||||||
CONF_PASSWORD: "SuperS3cr3t!",
|
CONF_PASSWORD: "SuperS3cr3t!",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_verisure_config_flow.login.mock_calls) == 2
|
assert len(mock_verisure_config_flow.login.mock_calls) == 1
|
||||||
assert len(mock_verisure_config_flow.login_mfa.mock_calls) == 1
|
assert len(mock_verisure_config_flow.request_mfa.mock_calls) == 1
|
||||||
assert len(mock_verisure_config_flow.mfa_validate.mock_calls) == 1
|
assert len(mock_verisure_config_flow.validate_mfa.mock_calls) == 1
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -215,9 +217,9 @@ async def test_full_user_flow_multiple_installations_with_mfa(
|
|||||||
CONF_PASSWORD: "SuperS3cr3t!",
|
CONF_PASSWORD: "SuperS3cr3t!",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_verisure_config_flow.login.mock_calls) == 2
|
assert len(mock_verisure_config_flow.login.mock_calls) == 1
|
||||||
assert len(mock_verisure_config_flow.login_mfa.mock_calls) == 1
|
assert len(mock_verisure_config_flow.request_mfa.mock_calls) == 1
|
||||||
assert len(mock_verisure_config_flow.mfa_validate.mock_calls) == 1
|
assert len(mock_verisure_config_flow.validate_mfa.mock_calls) == 1
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -257,7 +259,7 @@ async def test_verisure_errors(
|
|||||||
mock_verisure_config_flow.login.side_effect = VerisureLoginError(
|
mock_verisure_config_flow.login.side_effect = VerisureLoginError(
|
||||||
"Multifactor authentication enabled, disable or create MFA cookie"
|
"Multifactor authentication enabled, disable or create MFA cookie"
|
||||||
)
|
)
|
||||||
mock_verisure_config_flow.login_mfa.side_effect = side_effect
|
mock_verisure_config_flow.request_mfa.side_effect = side_effect
|
||||||
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
@ -268,7 +270,7 @@ async def test_verisure_errors(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_verisure_config_flow.login_mfa.side_effect = None
|
mock_verisure_config_flow.request_mfa.side_effect = None
|
||||||
|
|
||||||
assert result3.get("type") == FlowResultType.FORM
|
assert result3.get("type") == FlowResultType.FORM
|
||||||
assert result3.get("step_id") == "user"
|
assert result3.get("step_id") == "user"
|
||||||
@ -286,7 +288,7 @@ async def test_verisure_errors(
|
|||||||
assert result4.get("type") == FlowResultType.FORM
|
assert result4.get("type") == FlowResultType.FORM
|
||||||
assert result4.get("step_id") == "mfa"
|
assert result4.get("step_id") == "mfa"
|
||||||
|
|
||||||
mock_verisure_config_flow.mfa_validate.side_effect = side_effect
|
mock_verisure_config_flow.validate_mfa.side_effect = side_effect
|
||||||
|
|
||||||
result5 = await hass.config_entries.flow.async_configure(
|
result5 = await hass.config_entries.flow.async_configure(
|
||||||
result4["flow_id"],
|
result4["flow_id"],
|
||||||
@ -298,11 +300,11 @@ async def test_verisure_errors(
|
|||||||
assert result5.get("step_id") == "mfa"
|
assert result5.get("step_id") == "mfa"
|
||||||
assert result5.get("errors") == {"base": error}
|
assert result5.get("errors") == {"base": error}
|
||||||
|
|
||||||
mock_verisure_config_flow.installations = [
|
mock_verisure_config_flow.get_installations.return_value = {
|
||||||
mock_verisure_config_flow.installations[0]
|
k1: {k2: {k3: [v3[0]] for k3, v3 in v2.items()} for k2, v2 in v1.items()}
|
||||||
]
|
for k1, v1 in mock_verisure_config_flow.get_installations.return_value.items()
|
||||||
|
}
|
||||||
mock_verisure_config_flow.mfa_validate.side_effect = None
|
mock_verisure_config_flow.validate_mfa.side_effect = None
|
||||||
mock_verisure_config_flow.login.side_effect = None
|
mock_verisure_config_flow.login.side_effect = None
|
||||||
|
|
||||||
result6 = await hass.config_entries.flow.async_configure(
|
result6 = await hass.config_entries.flow.async_configure(
|
||||||
@ -321,9 +323,9 @@ async def test_verisure_errors(
|
|||||||
CONF_PASSWORD: "SuperS3cr3t!",
|
CONF_PASSWORD: "SuperS3cr3t!",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_verisure_config_flow.login.mock_calls) == 4
|
assert len(mock_verisure_config_flow.login.mock_calls) == 3
|
||||||
assert len(mock_verisure_config_flow.login_mfa.mock_calls) == 2
|
assert len(mock_verisure_config_flow.request_mfa.mock_calls) == 2
|
||||||
assert len(mock_verisure_config_flow.mfa_validate.mock_calls) == 2
|
assert len(mock_verisure_config_flow.validate_mfa.mock_calls) == 2
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -441,8 +443,8 @@ async def test_reauth_flow_with_mfa(
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_verisure_config_flow.login.mock_calls) == 2
|
assert len(mock_verisure_config_flow.login.mock_calls) == 2
|
||||||
assert len(mock_verisure_config_flow.login_mfa.mock_calls) == 1
|
assert len(mock_verisure_config_flow.request_mfa.mock_calls) == 1
|
||||||
assert len(mock_verisure_config_flow.mfa_validate.mock_calls) == 1
|
assert len(mock_verisure_config_flow.validate_mfa.mock_calls) == 1
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -491,7 +493,7 @@ async def test_reauth_flow_errors(
|
|||||||
mock_verisure_config_flow.login.side_effect = VerisureLoginError(
|
mock_verisure_config_flow.login.side_effect = VerisureLoginError(
|
||||||
"Multifactor authentication enabled, disable or create MFA cookie"
|
"Multifactor authentication enabled, disable or create MFA cookie"
|
||||||
)
|
)
|
||||||
mock_verisure_config_flow.login_mfa.side_effect = side_effect
|
mock_verisure_config_flow.request_mfa.side_effect = side_effect
|
||||||
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
@ -506,7 +508,7 @@ async def test_reauth_flow_errors(
|
|||||||
assert result3.get("step_id") == "reauth_confirm"
|
assert result3.get("step_id") == "reauth_confirm"
|
||||||
assert result3.get("errors") == {"base": "unknown_mfa"}
|
assert result3.get("errors") == {"base": "unknown_mfa"}
|
||||||
|
|
||||||
mock_verisure_config_flow.login_mfa.side_effect = None
|
mock_verisure_config_flow.request_mfa.side_effect = None
|
||||||
|
|
||||||
result4 = await hass.config_entries.flow.async_configure(
|
result4 = await hass.config_entries.flow.async_configure(
|
||||||
result3["flow_id"],
|
result3["flow_id"],
|
||||||
@ -520,7 +522,7 @@ async def test_reauth_flow_errors(
|
|||||||
assert result4.get("type") == FlowResultType.FORM
|
assert result4.get("type") == FlowResultType.FORM
|
||||||
assert result4.get("step_id") == "reauth_mfa"
|
assert result4.get("step_id") == "reauth_mfa"
|
||||||
|
|
||||||
mock_verisure_config_flow.mfa_validate.side_effect = side_effect
|
mock_verisure_config_flow.validate_mfa.side_effect = side_effect
|
||||||
|
|
||||||
result5 = await hass.config_entries.flow.async_configure(
|
result5 = await hass.config_entries.flow.async_configure(
|
||||||
result4["flow_id"],
|
result4["flow_id"],
|
||||||
@ -532,11 +534,12 @@ async def test_reauth_flow_errors(
|
|||||||
assert result5.get("step_id") == "reauth_mfa"
|
assert result5.get("step_id") == "reauth_mfa"
|
||||||
assert result5.get("errors") == {"base": error}
|
assert result5.get("errors") == {"base": error}
|
||||||
|
|
||||||
mock_verisure_config_flow.mfa_validate.side_effect = None
|
mock_verisure_config_flow.validate_mfa.side_effect = None
|
||||||
mock_verisure_config_flow.login.side_effect = None
|
mock_verisure_config_flow.login.side_effect = None
|
||||||
mock_verisure_config_flow.installations = [
|
mock_verisure_config_flow.get_installations.return_value = {
|
||||||
mock_verisure_config_flow.installations[0]
|
k1: {k2: {k3: [v3[0]] for k3, v3 in v2.items()} for k2, v2 in v1.items()}
|
||||||
]
|
for k1, v1 in mock_verisure_config_flow.get_installations.return_value.items()
|
||||||
|
}
|
||||||
|
|
||||||
await hass.config_entries.flow.async_configure(
|
await hass.config_entries.flow.async_configure(
|
||||||
result5["flow_id"],
|
result5["flow_id"],
|
||||||
@ -553,8 +556,8 @@ async def test_reauth_flow_errors(
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_verisure_config_flow.login.mock_calls) == 4
|
assert len(mock_verisure_config_flow.login.mock_calls) == 4
|
||||||
assert len(mock_verisure_config_flow.login_mfa.mock_calls) == 2
|
assert len(mock_verisure_config_flow.request_mfa.mock_calls) == 2
|
||||||
assert len(mock_verisure_config_flow.mfa_validate.mock_calls) == 2
|
assert len(mock_verisure_config_flow.validate_mfa.mock_calls) == 2
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user