mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Add zeroconf discovery to Hot Spring (#179457)
Co-authored-by: Moustachauve <2206577+Moustachauve@users.noreply.github.com>
This commit is contained in:
co-authored by
Moustachauve
parent
6f5c6c091e
commit
d4c7e19419
@@ -15,6 +15,7 @@ from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import TextSelector
|
||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
@@ -38,6 +39,9 @@ class HotSpringConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Hot Spring."""
|
||||
|
||||
VERSION = 1
|
||||
discovered_host: str
|
||||
discovered_spa: Spa
|
||||
discovered_title: str
|
||||
|
||||
@override
|
||||
async def async_step_user(
|
||||
@@ -86,3 +90,37 @@ class HotSpringConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration of the Hot Spring spa."""
|
||||
return await self.async_step_user(user_input)
|
||||
|
||||
@override
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: ZeroconfServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
self.discovered_host = discovery_info.host
|
||||
try:
|
||||
self.discovered_spa = await validate_input(
|
||||
self.hass, {CONF_HOST: discovery_info.host}
|
||||
)
|
||||
except HotSpringConnectionError, HotSpringError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
await self.async_set_unique_id(self.discovered_spa.info.mac_address)
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host})
|
||||
|
||||
self.discovered_title = self.discovered_spa.info.hostname or "Hot Spring Spa"
|
||||
self.context["title_placeholders"] = {"name": self.discovered_title}
|
||||
|
||||
self._set_confirm_only()
|
||||
return self.async_show_form(
|
||||
step_id="zeroconf_confirm",
|
||||
description_placeholders={"name": self.discovered_title},
|
||||
)
|
||||
|
||||
async def async_step_zeroconf_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initiated by zeroconf."""
|
||||
return self.async_create_entry(
|
||||
title=self.discovered_title,
|
||||
data={CONF_HOST: self.discovered_host},
|
||||
)
|
||||
|
||||
@@ -8,5 +8,11 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["hotspring"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["python-hotspring==1.3.0"]
|
||||
"requirements": ["python-hotspring==1.3.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "watkins_spa*",
|
||||
"type": "_ws._tcp.local."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ rules:
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
discovery-update-info: todo
|
||||
discovery: todo
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations: done
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
"host": "Hostname or IP address of your Hot Spring Home Network Adapter (HNA)."
|
||||
},
|
||||
"description": "Set up your Hot Spring Home Network Adapter (HNA) to integrate with Home Assistant."
|
||||
},
|
||||
"zeroconf_confirm": {
|
||||
"description": "Do you want to add the Hot Spring spa named `{name}` to Home Assistant?",
|
||||
"title": "Discovered Hot Spring spa"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+6
@@ -1067,6 +1067,12 @@ ZEROCONF = {
|
||||
"domain": "wled",
|
||||
},
|
||||
],
|
||||
"_ws._tcp.local.": [
|
||||
{
|
||||
"domain": "hotspring",
|
||||
"name": "watkins_spa*",
|
||||
},
|
||||
],
|
||||
"_wyoming._tcp.local.": [
|
||||
{
|
||||
"domain": "wyoming",
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
"""Tests for the Hot Spring config flow."""
|
||||
|
||||
import dataclasses
|
||||
from ipaddress import ip_address
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from hotspring import HotSpringConnectionError, HotSpringError, Spa
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.hotspring.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||
|
||||
from tests.common import MockConfigEntry, get_schema_suggested_value
|
||||
|
||||
MOCK_ZEROCONF_DATA = ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.100"),
|
||||
ip_addresses=[ip_address("192.168.1.100")],
|
||||
hostname="Watkins_SpaAABBCCDDEEFF.local.",
|
||||
name="Watkins_SpaAABBCCDDEEFF._ws._tcp.local.",
|
||||
port=80,
|
||||
properties={},
|
||||
type="_ws._tcp.local.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry", "mock_hotspring")
|
||||
async def test_full_user_flow_implementation(hass: HomeAssistant) -> None:
|
||||
@@ -121,6 +134,71 @@ async def test_form_no_mac_address(
|
||||
assert result["result"].unique_id == "AA:BB:CC:DD:EE:FF"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry", "mock_hotspring")
|
||||
async def test_full_zeroconf_flow_implementation(hass: HomeAssistant) -> None:
|
||||
"""Test the full zeroconf flow from start to finish."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
|
||||
assert result["step_id"] == "zeroconf_confirm"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["description_placeholders"] == {"name": "ConnectedSpa_DDEEFF"}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result["title"] == "ConnectedSpa_DDEEFF"
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {CONF_HOST: "192.168.1.100"}
|
||||
assert result["result"].unique_id == "AA:BB:CC:DD:EE:FF"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception",
|
||||
[HotSpringConnectionError, HotSpringError],
|
||||
)
|
||||
async def test_zeroconf_connection_error(
|
||||
hass: HomeAssistant, mock_hotspring: MagicMock, exception: type[Exception]
|
||||
) -> None:
|
||||
"""Test we abort zeroconf flow on Hot Spring connection error."""
|
||||
mock_hotspring.update.side_effect = exception
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_hotspring")
|
||||
async def test_zeroconf_device_already_configured(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test we abort zeroconf flow and update host if already configured."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=dataclasses.replace(
|
||||
MOCK_ZEROCONF_DATA,
|
||||
ip_address=ip_address("192.168.1.200"),
|
||||
ip_addresses=[ip_address("192.168.1.200")],
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert mock_config_entry.data[CONF_HOST] == "192.168.1.200"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
async def test_full_reconfigure_flow_success(
|
||||
hass: HomeAssistant,
|
||||
|
||||
Reference in New Issue
Block a user