Add Reolink dhcp discovery (#85880)

Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
starkillerOG 2023-01-22 21:09:18 +01:00 committed by GitHub
parent 9c76cd1b6a
commit 32c1a01159
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 7 deletions

View File

@ -9,10 +9,12 @@ from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkErr
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import dhcp
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import format_mac
from .const import CONF_PROTOCOL, CONF_USE_HTTPS, DOMAIN
from .exceptions import ReolinkException, UserNotAdmin
@ -87,6 +89,21 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self.async_step_user()
return self.async_show_form(step_id="reauth_confirm")
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
"""Handle discovery via dhcp."""
mac_address = format_mac(discovery_info.macaddress)
await self.async_set_unique_id(mac_address)
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
short_mac = mac_address[-8:].upper()
self.context["title_placeholders"] = {
"short_mac": short_mac,
"ip_address": discovery_info.ip,
}
self._host = discovery_info.ip
return await self.async_step_user()
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
@ -95,6 +112,9 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
placeholders = {"error": ""}
if user_input is not None:
if CONF_HOST not in user_input:
user_input[CONF_HOST] = self._host
host = ReolinkHost(self.hass, user_input, DEFAULT_OPTIONS)
try:
await host.async_init()
@ -144,9 +164,14 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
{
vol.Required(CONF_USERNAME, default=self._username): str,
vol.Required(CONF_PASSWORD, default=self._password): str,
vol.Required(CONF_HOST, default=self._host): str,
}
)
if self._host is None or errors:
data_schema = data_schema.extend(
{
vol.Required(CONF_HOST, default=self._host): str,
}
)
if errors:
data_schema = data_schema.extend(
{

View File

@ -7,5 +7,11 @@
"dependencies": ["webhook"],
"codeowners": ["@starkillerOG"],
"iot_class": "local_polling",
"loggers": ["reolink_aio"]
"loggers": ["reolink_aio"],
"dhcp": [
{
"hostname": "reolink*",
"macaddress": "EC71DB*"
}
]
}

View File

@ -1,5 +1,6 @@
{
"config": {
"flow_title": "{short_mac} ({ip_address})",
"step": {
"user": {
"description": "{error}",

View File

@ -11,6 +11,7 @@
"not_admin": "User needs to be admin, user ''{username}'' has authorisation level ''{userlevel}''",
"unknown": "Unexpected error"
},
"flow_title": "{short_mac} ({ip_address})",
"step": {
"reauth_confirm": {
"description": "The Reolink integration needs to re-authenticate your connection details",

View File

@ -384,6 +384,11 @@ DHCP: list[dict[str, str | bool]] = [
"domain": "rainforest_eagle",
"macaddress": "D8D5B9*",
},
{
"domain": "reolink",
"hostname": "reolink*",
"macaddress": "EC71DB*",
},
{
"domain": "ring",
"hostname": "ring*",

View File

@ -6,6 +6,7 @@ import pytest
from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError
from homeassistant import config_entries, data_entry_flow
from homeassistant.components import dhcp
from homeassistant.components.reolink import const
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
@ -314,7 +315,7 @@ async def test_reauth(hass):
data=config_entry.data,
)
assert result["type"] == "form"
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(
@ -322,21 +323,94 @@ async def test_reauth(hass):
{},
)
assert result["type"] == "form"
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: TEST_HOST2,
CONF_USERNAME: TEST_USERNAME2,
CONF_PASSWORD: TEST_PASSWORD2,
},
)
assert result["type"] == "abort"
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert config_entry.data[CONF_HOST] == TEST_HOST2
assert config_entry.data[CONF_HOST] == TEST_HOST
assert config_entry.data[CONF_USERNAME] == TEST_USERNAME2
assert config_entry.data[CONF_PASSWORD] == TEST_PASSWORD2
async def test_dhcp_flow(hass):
"""Successful flow from DHCP discovery."""
dhcp_data = dhcp.DhcpServiceInfo(
ip=TEST_HOST,
hostname="Reolink",
macaddress=TEST_MAC,
)
result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_data
)
assert result["type"] == data_entry_flow.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,
},
)
assert result["type"] is data_entry_flow.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,
const.CONF_USE_HTTPS: TEST_USE_HTTPS,
}
assert result["options"] == {
const.CONF_PROTOCOL: DEFAULT_PROTOCOL,
}
async def test_dhcp_abort_flow(hass):
"""Test dhcp discovery aborts if already configured."""
config_entry = MockConfigEntry(
domain=const.DOMAIN,
unique_id=format_mac(TEST_MAC),
data={
CONF_HOST: TEST_HOST,
CONF_USERNAME: TEST_USERNAME,
CONF_PASSWORD: TEST_PASSWORD,
CONF_PORT: TEST_PORT,
const.CONF_USE_HTTPS: TEST_USE_HTTPS,
},
options={
const.CONF_PROTOCOL: DEFAULT_PROTOCOL,
},
title=TEST_NVR_NAME,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
dhcp_data = dhcp.DhcpServiceInfo(
ip=TEST_HOST,
hostname="Reolink",
macaddress=TEST_MAC,
)
result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_data
)
assert result["type"] is data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "already_configured"