mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 01:38:02 +00:00
Add DHCP discovery support to Bond (#142372)
* Add DHCP discovery support to Bond * fixes * unique ids are always upper * raise_on_progress=False for user * Update tests/components/bond/test_config_flow.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * assert unique id --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
0a7b4d18dc
commit
dcef86a30d
@ -16,6 +16,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -91,11 +92,22 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
self._discovered[CONF_ACCESS_TOKEN] = token
|
self._discovered[CONF_ACCESS_TOKEN] = token
|
||||||
try:
|
try:
|
||||||
_, hub_name = await _validate_input(self.hass, self._discovered)
|
bond_id, hub_name = await _validate_input(self.hass, self._discovered)
|
||||||
except InputValidationError:
|
except InputValidationError:
|
||||||
return
|
return
|
||||||
|
await self.async_set_unique_id(bond_id)
|
||||||
|
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
|
||||||
self._discovered[CONF_NAME] = hub_name
|
self._discovered[CONF_NAME] = hub_name
|
||||||
|
|
||||||
|
async def async_step_dhcp(
|
||||||
|
self, discovery_info: DhcpServiceInfo
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle a flow initialized by dhcp discovery."""
|
||||||
|
host = discovery_info.ip
|
||||||
|
bond_id = discovery_info.hostname.partition("-")[2].upper()
|
||||||
|
await self.async_set_unique_id(bond_id)
|
||||||
|
return await self.async_step_any_discovery(bond_id, host)
|
||||||
|
|
||||||
async def async_step_zeroconf(
|
async def async_step_zeroconf(
|
||||||
self, discovery_info: ZeroconfServiceInfo
|
self, discovery_info: ZeroconfServiceInfo
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
@ -104,11 +116,17 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
host: str = discovery_info.host
|
host: str = discovery_info.host
|
||||||
bond_id = name.partition(".")[0]
|
bond_id = name.partition(".")[0]
|
||||||
await self.async_set_unique_id(bond_id)
|
await self.async_set_unique_id(bond_id)
|
||||||
|
return await self.async_step_any_discovery(bond_id, host)
|
||||||
|
|
||||||
|
async def async_step_any_discovery(
|
||||||
|
self, bond_id: str, host: str
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle a flow initialized by discovery."""
|
||||||
for entry in self._async_current_entries():
|
for entry in self._async_current_entries():
|
||||||
if entry.unique_id != bond_id:
|
if entry.unique_id != bond_id:
|
||||||
continue
|
continue
|
||||||
updates = {CONF_HOST: host}
|
updates = {CONF_HOST: host}
|
||||||
if entry.state == ConfigEntryState.SETUP_ERROR and (
|
if entry.state is ConfigEntryState.SETUP_ERROR and (
|
||||||
token := await async_get_token(self.hass, host)
|
token := await async_get_token(self.hass, host)
|
||||||
):
|
):
|
||||||
updates[CONF_ACCESS_TOKEN] = token
|
updates[CONF_ACCESS_TOKEN] = token
|
||||||
@ -153,10 +171,14 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_HOST: self._discovered[CONF_HOST],
|
CONF_HOST: self._discovered[CONF_HOST],
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
_, hub_name = await _validate_input(self.hass, data)
|
bond_id, hub_name = await _validate_input(self.hass, data)
|
||||||
except InputValidationError as error:
|
except InputValidationError as error:
|
||||||
errors["base"] = error.base
|
errors["base"] = error.base
|
||||||
else:
|
else:
|
||||||
|
await self.async_set_unique_id(bond_id)
|
||||||
|
self._abort_if_unique_id_configured(
|
||||||
|
updates={CONF_HOST: self._discovered[CONF_HOST]}
|
||||||
|
)
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=hub_name,
|
title=hub_name,
|
||||||
data=data,
|
data=data,
|
||||||
@ -185,8 +207,10 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
except InputValidationError as error:
|
except InputValidationError as error:
|
||||||
errors["base"] = error.base
|
errors["base"] = error.base
|
||||||
else:
|
else:
|
||||||
await self.async_set_unique_id(bond_id)
|
await self.async_set_unique_id(bond_id, raise_on_progress=False)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured(
|
||||||
|
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||||
|
)
|
||||||
return self.async_create_entry(title=hub_name, data=user_input)
|
return self.async_create_entry(title=hub_name, data=user_input)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -3,6 +3,16 @@
|
|||||||
"name": "Bond",
|
"name": "Bond",
|
||||||
"codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"],
|
"codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
"dhcp": [
|
||||||
|
{
|
||||||
|
"hostname": "bond-*",
|
||||||
|
"macaddress": "3C6A2C1*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hostname": "bond-*",
|
||||||
|
"macaddress": "F44E38*"
|
||||||
|
}
|
||||||
|
],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/bond",
|
"documentation": "https://www.home-assistant.io/integrations/bond",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["bond_async"],
|
"loggers": ["bond_async"],
|
||||||
|
10
homeassistant/generated/dhcp.py
generated
10
homeassistant/generated/dhcp.py
generated
@ -84,6 +84,16 @@ DHCP: Final[list[dict[str, str | bool]]] = [
|
|||||||
"hostname": "blink*",
|
"hostname": "blink*",
|
||||||
"macaddress": "20A171*",
|
"macaddress": "20A171*",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"domain": "bond",
|
||||||
|
"hostname": "bond-*",
|
||||||
|
"macaddress": "3C6A2C1*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "bond",
|
||||||
|
"hostname": "bond-*",
|
||||||
|
"macaddress": "F44E38*",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"domain": "broadlink",
|
"domain": "broadlink",
|
||||||
"registered_devices": True,
|
"registered_devices": True,
|
||||||
|
@ -15,6 +15,8 @@ from homeassistant.config_entries import ConfigEntryState
|
|||||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
|
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
from homeassistant.helpers.device_registry import format_mac
|
||||||
|
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
@ -63,6 +65,59 @@ async def test_user_form(hass: HomeAssistant) -> None:
|
|||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_form_can_create_when_already_discovered(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test we get the user initiated form can create when already discovered."""
|
||||||
|
|
||||||
|
with patch_bond_version(), patch_bond_token():
|
||||||
|
zc_result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data=ZeroconfServiceInfo(
|
||||||
|
ip_address=ip_address("127.0.0.1"),
|
||||||
|
ip_addresses=[ip_address("127.0.0.1")],
|
||||||
|
hostname="mock_hostname",
|
||||||
|
name="ZXXX12345.some-other-tail-info",
|
||||||
|
port=None,
|
||||||
|
properties={},
|
||||||
|
type="mock_type",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert zc_result["type"] is FlowResultType.FORM
|
||||||
|
assert zc_result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch_bond_version(return_value={"bondid": "ZXXX12345"}),
|
||||||
|
patch_bond_device_ids(return_value=["f6776c11", "f6776c12"]),
|
||||||
|
patch_bond_bridge(),
|
||||||
|
patch_bond_device_properties(),
|
||||||
|
patch_bond_device(),
|
||||||
|
patch_bond_device_state(),
|
||||||
|
_patch_async_setup_entry() as mock_setup_entry,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result2["title"] == "bond-name"
|
||||||
|
assert result2["data"] == {
|
||||||
|
CONF_HOST: "some host",
|
||||||
|
CONF_ACCESS_TOKEN: "test-token",
|
||||||
|
}
|
||||||
|
assert result2["result"].unique_id == "ZXXX12345"
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_user_form_with_non_bridge(hass: HomeAssistant) -> None:
|
async def test_user_form_with_non_bridge(hass: HomeAssistant) -> None:
|
||||||
"""Test setup a smart by bond fan."""
|
"""Test setup a smart by bond fan."""
|
||||||
|
|
||||||
@ -97,6 +152,7 @@ async def test_user_form_with_non_bridge(hass: HomeAssistant) -> None:
|
|||||||
CONF_HOST: "some host",
|
CONF_HOST: "some host",
|
||||||
CONF_ACCESS_TOKEN: "test-token",
|
CONF_ACCESS_TOKEN: "test-token",
|
||||||
}
|
}
|
||||||
|
assert result2["result"].unique_id == "KXXX12345"
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -253,6 +309,107 @@ async def test_zeroconf_form(hass: HomeAssistant) -> None:
|
|||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dhcp_discovery(hass: HomeAssistant) -> None:
|
||||||
|
"""Test DHCP discovery."""
|
||||||
|
|
||||||
|
with patch_bond_version(), patch_bond_token():
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
|
data=DhcpServiceInfo(
|
||||||
|
ip="127.0.0.1",
|
||||||
|
hostname="Bond-KVPRBDJ45842",
|
||||||
|
macaddress=format_mac("3c:6a:2c:1c:8c:80"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch_bond_version(return_value={"bondid": "KVPRBDJ45842"}),
|
||||||
|
patch_bond_bridge(),
|
||||||
|
patch_bond_device_ids(),
|
||||||
|
_patch_async_setup_entry() as mock_setup_entry,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_ACCESS_TOKEN: "test-token"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result2["title"] == "bond-name"
|
||||||
|
assert result2["data"] == {
|
||||||
|
CONF_HOST: "127.0.0.1",
|
||||||
|
CONF_ACCESS_TOKEN: "test-token",
|
||||||
|
}
|
||||||
|
assert result2["result"].unique_id == "KVPRBDJ45842"
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dhcp_discovery_already_exists(hass: HomeAssistant) -> None:
|
||||||
|
"""Test DHCP discovery for an already existing entry."""
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="KVPRBDJ45842",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch_bond_version(return_value={"bondid": "KVPRBDJ45842"}),
|
||||||
|
patch_bond_token(),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
|
data=DhcpServiceInfo(
|
||||||
|
ip="127.0.0.1",
|
||||||
|
hostname="Bond-KVPRBDJ45842".lower(),
|
||||||
|
macaddress=format_mac("3c:6a:2c:1c:8c:80"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dhcp_discovery_short_name(hass: HomeAssistant) -> None:
|
||||||
|
"""Test DHCP discovery with the name cut off."""
|
||||||
|
|
||||||
|
with patch_bond_version(), patch_bond_token():
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_DHCP},
|
||||||
|
data=DhcpServiceInfo(
|
||||||
|
ip="127.0.0.1",
|
||||||
|
hostname="Bond-KVPRBDJ",
|
||||||
|
macaddress=format_mac("3c:6a:2c:1c:8c:80"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch_bond_version(return_value={"bondid": "KVPRBDJ45842"}),
|
||||||
|
patch_bond_bridge(),
|
||||||
|
patch_bond_device_ids(),
|
||||||
|
_patch_async_setup_entry() as mock_setup_entry,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_ACCESS_TOKEN: "test-token"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result2["title"] == "bond-name"
|
||||||
|
assert result2["data"] == {
|
||||||
|
CONF_HOST: "127.0.0.1",
|
||||||
|
CONF_ACCESS_TOKEN: "test-token",
|
||||||
|
}
|
||||||
|
assert result2["result"].unique_id == "KVPRBDJ45842"
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_form_token_unavailable(hass: HomeAssistant) -> None:
|
async def test_zeroconf_form_token_unavailable(hass: HomeAssistant) -> None:
|
||||||
"""Test we get the discovery form and we handle the token being unavailable."""
|
"""Test we get the discovery form and we handle the token being unavailable."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user