Ask for permission to disable Reolink privacy mode during config flow (#136511)

This commit is contained in:
starkillerOG 2025-01-27 10:52:48 +01:00 committed by GitHub
parent 439a393816
commit f1dfae6937
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 1 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Mapping
import logging
from typing import Any
@ -11,6 +12,7 @@ from reolink_aio.exceptions import (
ApiError,
CredentialsInvalidError,
LoginFirmwareError,
LoginPrivacyModeError,
ReolinkError,
)
import voluptuous as vol
@ -49,6 +51,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_PROTOCOL = "rtsp"
DEFAULT_OPTIONS = {CONF_PROTOCOL: DEFAULT_PROTOCOL}
API_STARTUP_TIME = 5
class ReolinkOptionsFlowHandler(OptionsFlow):
@ -101,6 +104,8 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN):
self._host: str | None = None
self._username: str = "admin"
self._password: str | None = None
self._user_input: dict[str, Any] | None = None
self._disable_privacy: bool = False
@staticmethod
@callback
@ -198,6 +203,21 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN):
self._host = discovery_info.ip
return await self.async_step_user()
async def async_step_privacy(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask permission to disable privacy mode."""
if user_input is not None:
self._disable_privacy = True
return await self.async_step_user(self._user_input)
assert self._user_input is not None
placeholders = {"host": self._user_input[CONF_HOST]}
return self.async_show_form(
step_id="privacy",
description_placeholders=placeholders,
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@ -219,6 +239,10 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN):
host = ReolinkHost(self.hass, user_input, DEFAULT_OPTIONS)
try:
if self._disable_privacy:
await host.api.baichuan.set_privacy_mode(enable=False)
# give the camera some time to startup the HTTP API server
await asyncio.sleep(API_STARTUP_TIME)
await host.async_init()
except UserNotAdmin:
errors[CONF_USERNAME] = "not_admin"
@ -227,6 +251,9 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN):
except PasswordIncompatible:
errors[CONF_PASSWORD] = "password_incompatible"
placeholders["special_chars"] = ALLOWED_SPECIAL_CHARS
except LoginPrivacyModeError:
self._user_input = user_input
return await self.async_step_privacy()
except CredentialsInvalidError:
errors[CONF_PASSWORD] = "invalid_auth"
except LoginFirmwareError:

View File

@ -18,6 +18,10 @@
"username": "Username to login to the Reolink device itself. Not the Reolink cloud account.",
"password": "Password to login to the Reolink device itself. Not the Reolink cloud account."
}
},
"privacy": {
"title": "Permission to disable Reolink privacy mode",
"description": "Privacy mode is enabled on Reolink device {host}. By pressing SUBMIT, the privacy mode will be disabled to retrieve the necessary information from the Reolink device. You can abort the setup by pressing X and repeat the setup at a time in which privacy mode can be disabled. After this configuration, you are free to enable the privacy mode again using the privacy mode switch entity. During normal startup the privacy mode will not be disabled. Note however that all entities will be marked unavailable as long as the privacy mode is active."
}
},
"error": {

View File

@ -2,7 +2,7 @@
import json
from typing import Any
from unittest.mock import ANY, AsyncMock, MagicMock, call
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
from aiohttp import ClientSession
from freezegun.api import FrozenDateTimeFactory
@ -11,6 +11,7 @@ from reolink_aio.exceptions import (
ApiError,
CredentialsInvalidError,
LoginFirmwareError,
LoginPrivacyModeError,
ReolinkError,
)
@ -88,6 +89,59 @@ async def test_config_flow_manual_success(
assert result["result"].unique_id == TEST_MAC
async def test_config_flow_privacy_success(
hass: HomeAssistant, reolink_connect: MagicMock, mock_setup_entry: MagicMock
) -> None:
"""Successful flow when privacy mode is turned on."""
reolink_connect.baichuan.privacy_mode.return_value = True
reolink_connect.get_host_data.side_effect = LoginPrivacyModeError("Test error")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: TEST_USERNAME,
CONF_PASSWORD: TEST_PASSWORD,
CONF_HOST: TEST_HOST,
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "privacy"
assert result["errors"] is None
assert reolink_connect.baichuan.set_privacy_mode.call_count == 0
reolink_connect.get_host_data.reset_mock(side_effect=True)
with patch("homeassistant.components.reolink.config_flow.API_STARTUP_TIME", new=0):
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert reolink_connect.baichuan.set_privacy_mode.call_count == 1
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == TEST_NVR_NAME
assert result["data"] == {
CONF_HOST: TEST_HOST,
CONF_USERNAME: TEST_USERNAME,
CONF_PASSWORD: TEST_PASSWORD,
CONF_PORT: TEST_PORT,
CONF_USE_HTTPS: TEST_USE_HTTPS,
}
assert result["options"] == {
CONF_PROTOCOL: DEFAULT_PROTOCOL,
}
assert result["result"].unique_id == TEST_MAC
reolink_connect.baichuan.privacy_mode.return_value = False
async def test_config_flow_errors(
hass: HomeAssistant, reolink_connect: MagicMock, mock_setup_entry: MagicMock
) -> None: