mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add blebox discovery/zeroconf (#83837)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
e4c610af62
commit
c737378ee1
@ -83,6 +83,7 @@ class BleBoxEntity(Entity, Generic[_FeatureT]):
|
|||||||
model=product.model,
|
model=product.model,
|
||||||
name=product.name,
|
name=product.name,
|
||||||
sw_version=product.firmware_version,
|
sw_version=product.firmware_version,
|
||||||
|
configuration_url=f"http://{product.address}",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
"""Config flow for BleBox devices integration."""
|
"""Config flow for BleBox devices integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from blebox_uniapi.box import Box
|
from blebox_uniapi.box import Box
|
||||||
from blebox_uniapi.error import Error, UnsupportedBoxVersion
|
from blebox_uniapi.error import Error, UnsupportedBoxResponse, UnsupportedBoxVersion
|
||||||
from blebox_uniapi.session import ApiHost
|
from blebox_uniapi.session import ApiHost
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -74,9 +79,67 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
description_placeholders={"address": f"{host}:{port}"},
|
description_placeholders={"address": f"{host}:{port}"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_zeroconf(
|
||||||
|
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle zeroconf discovery."""
|
||||||
|
hass = self.hass
|
||||||
|
ipaddress = host_port(discovery_info.__dict__)
|
||||||
|
self.device_config["host"] = discovery_info.host
|
||||||
|
self.device_config["port"] = discovery_info.port
|
||||||
|
|
||||||
|
websession = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
api_host = ApiHost(
|
||||||
|
*ipaddress, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
product = await Box.async_from_host(api_host)
|
||||||
|
except UnsupportedBoxVersion:
|
||||||
|
return self.async_abort(reason="unsupported_device_version")
|
||||||
|
except UnsupportedBoxResponse:
|
||||||
|
return self.async_abort(reason="unsupported_device_response")
|
||||||
|
|
||||||
|
self.device_config["name"] = product.name
|
||||||
|
# Check if configured but IP changed since
|
||||||
|
await self.async_set_unique_id(product.unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
self.context.update(
|
||||||
|
{
|
||||||
|
"title_placeholders": {
|
||||||
|
"name": self.device_config["name"],
|
||||||
|
"host": self.device_config["host"],
|
||||||
|
},
|
||||||
|
"configuration_url": f"http://{discovery_info.host}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return await self.async_step_confirm_discovery()
|
||||||
|
|
||||||
|
async def async_step_confirm_discovery(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle discovery confirmation."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self.device_config["name"],
|
||||||
|
data={
|
||||||
|
"host": self.device_config["host"],
|
||||||
|
"port": self.device_config["port"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="confirm_discovery",
|
||||||
|
description_placeholders={
|
||||||
|
"name": self.device_config["name"],
|
||||||
|
"host": self.device_config["host"],
|
||||||
|
"port": self.device_config["port"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle initial user-triggered config step."""
|
"""Handle initial user-triggered config step."""
|
||||||
|
|
||||||
hass = self.hass
|
hass = self.hass
|
||||||
schema = create_schema(user_input)
|
schema = create_schema(user_input)
|
||||||
|
|
||||||
@ -97,7 +160,6 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
reason=ADDRESS_ALREADY_CONFIGURED,
|
reason=ADDRESS_ALREADY_CONFIGURED,
|
||||||
description_placeholders={"address": f"{host}:{port}"},
|
description_placeholders={"address": f"{host}:{port}"},
|
||||||
)
|
)
|
||||||
|
|
||||||
websession = async_get_clientsession(hass)
|
websession = async_get_clientsession(hass)
|
||||||
api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER)
|
api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER)
|
||||||
try:
|
try:
|
||||||
@ -119,7 +181,7 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check if configured but IP changed since
|
# Check if configured but IP changed since
|
||||||
await self.async_set_unique_id(product.unique_id)
|
await self.async_set_unique_id(product.unique_id, raise_on_progress=False)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return self.async_create_entry(title=product.name, data=user_input)
|
return self.async_create_entry(title=product.name, data=user_input)
|
||||||
|
@ -6,5 +6,6 @@
|
|||||||
"requirements": ["blebox_uniapi==2.1.3"],
|
"requirements": ["blebox_uniapi==2.1.3"],
|
||||||
"codeowners": ["@bbx-a", "@riokuu"],
|
"codeowners": ["@bbx-a", "@riokuu"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["blebox_uniapi"]
|
"loggers": ["blebox_uniapi"],
|
||||||
|
"zeroconf": ["_bbxsrv._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,15 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "IP Address",
|
"host": "IP address / domain name / mDNS address",
|
||||||
"port": "Port"
|
"port": "Port"
|
||||||
},
|
},
|
||||||
"description": "Set up your BleBox to integrate with Home Assistant.",
|
"description": "Set up your BleBox to integrate with Home Assistant.",
|
||||||
"title": "Set up your BleBox device"
|
"title": "Set up your BleBox device"
|
||||||
|
},
|
||||||
|
"confirm_discovery": {
|
||||||
|
"description": "Would you like to configure {name} {host}:{port}?."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,11 @@ ZEROCONF = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"_bbxsrv._tcp.local.": [
|
||||||
|
{
|
||||||
|
"domain": "blebox",
|
||||||
|
},
|
||||||
|
],
|
||||||
"_bond._tcp.local.": [
|
"_bond._tcp.local.": [
|
||||||
{
|
{
|
||||||
"domain": "bond",
|
"domain": "bond",
|
||||||
|
@ -6,10 +6,14 @@ import blebox_uniapi
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.components.blebox import config_flow
|
from homeassistant.components.blebox import config_flow
|
||||||
|
from homeassistant.const import CONF_IP_ADDRESS
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .conftest import mock_config, mock_only_feature, setup_product_mock
|
from ...common import MockConfigEntry
|
||||||
|
from .conftest import mock_config, mock_feature, mock_only_feature, setup_product_mock
|
||||||
|
|
||||||
|
|
||||||
def create_valid_feature_mock(path="homeassistant.components.blebox.Products"):
|
def create_valid_feature_mock(path="homeassistant.components.blebox.Products"):
|
||||||
@ -190,3 +194,110 @@ async def test_async_remove_entry(hass, valid_feature_mock):
|
|||||||
|
|
||||||
assert hass.config_entries.async_entries() == []
|
assert hass.config_entries.async_entries() == []
|
||||||
assert config.state is config_entries.ConfigEntryState.NOT_LOADED
|
assert config.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_with_zeroconf(hass):
|
||||||
|
"""Test setup from zeroconf discovery."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
config_flow.DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data=zeroconf.ZeroconfServiceInfo(
|
||||||
|
host="172.100.123.4",
|
||||||
|
addresses=["172.100.123.4"],
|
||||||
|
port=80,
|
||||||
|
hostname="bbx-bbtest123456.local.",
|
||||||
|
type="_bbxsrv._tcp.local.",
|
||||||
|
name="bbx-bbtest123456._bbxsrv._tcp.local.",
|
||||||
|
properties={"_raw": {}},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
|
||||||
|
with patch("homeassistant.components.blebox.async_setup_entry", return_value=True):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result2["data"] == {"host": "172.100.123.4", "port": 80}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_with_zeroconf_when_already_configured(hass):
|
||||||
|
"""Test behaviour if device already configured."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=config_flow.DOMAIN,
|
||||||
|
data={CONF_IP_ADDRESS: "172.100.123.4"},
|
||||||
|
unique_id="abcd0123ef5678",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
feature: AsyncMock = mock_feature(
|
||||||
|
"sensors",
|
||||||
|
blebox_uniapi.sensor.Temperature,
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.blebox.config_flow.Box.async_from_host",
|
||||||
|
return_value=feature.product,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_init(
|
||||||
|
config_flow.DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data=zeroconf.ZeroconfServiceInfo(
|
||||||
|
host="172.100.123.4",
|
||||||
|
addresses=["172.100.123.4"],
|
||||||
|
port=80,
|
||||||
|
hostname="bbx-bbtest123456.local.",
|
||||||
|
type="_bbxsrv._tcp.local.",
|
||||||
|
name="bbx-bbtest123456._bbxsrv._tcp.local.",
|
||||||
|
properties={"_raw": {}},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == FlowResultType.ABORT
|
||||||
|
assert result2["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_with_zeroconf_when_device_unsupported(hass):
|
||||||
|
"""Test behaviour when device is not supported."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.blebox.config_flow.Box.async_from_host",
|
||||||
|
side_effect=blebox_uniapi.error.UnsupportedBoxVersion,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
config_flow.DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data=zeroconf.ZeroconfServiceInfo(
|
||||||
|
host="172.100.123.4",
|
||||||
|
addresses=["172.100.123.4"],
|
||||||
|
port=80,
|
||||||
|
hostname="bbx-bbtest123456.local.",
|
||||||
|
type="_bbxsrv._tcp.local.",
|
||||||
|
name="bbx-bbtest123456._bbxsrv._tcp.local.",
|
||||||
|
properties={"_raw": {}},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "unsupported_device_version"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_with_zeroconf_when_device_response_unsupported(hass):
|
||||||
|
"""Test behaviour when device returned unsupported response."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.blebox.config_flow.Box.async_from_host",
|
||||||
|
side_effect=blebox_uniapi.error.UnsupportedBoxResponse,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
config_flow.DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data=zeroconf.ZeroconfServiceInfo(
|
||||||
|
host="172.100.123.4",
|
||||||
|
addresses=["172.100.123.4"],
|
||||||
|
port=80,
|
||||||
|
hostname="bbx-bbtest123456.local.",
|
||||||
|
type="_bbxsrv._tcp.local.",
|
||||||
|
name="bbx-bbtest123456._bbxsrv._tcp.local.",
|
||||||
|
properties={"_raw": {}},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "unsupported_device_response"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user