Add Plugwise zeroconf discovery (#37289)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Tom Scholten <tom@sue.nl> Co-authored-by: Tom <CoMPaTech@users.noreply.github.com>pull/37445/head
parent
80aebcc7d2
commit
d57dbb4319
|
@ -7,32 +7,50 @@ import voluptuous as vol
|
||||||
from homeassistant import config_entries, core, exceptions
|
from homeassistant import config_entries, core, exceptions
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
|
|
||||||
from .const import DOMAIN # pylint:disable=unused-import
|
from .const import DOMAIN # pylint:disable=unused-import
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DATA_SCHEMA = vol.Schema(
|
ZEROCONF_MAP = {
|
||||||
{vol.Required(CONF_HOST): str, vol.Required(CONF_PASSWORD): str}
|
"smile": "P1 DSMR",
|
||||||
)
|
"smile_thermo": "Climate (Anna)",
|
||||||
|
"smile_open_therm": "Climate (Adam)",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _base_schema(discovery_info):
|
||||||
|
"""Generate base schema."""
|
||||||
|
base_schema = {}
|
||||||
|
|
||||||
|
if not discovery_info:
|
||||||
|
base_schema[vol.Required(CONF_HOST)] = str
|
||||||
|
|
||||||
|
base_schema[vol.Required(CONF_PASSWORD)] = str
|
||||||
|
|
||||||
|
return vol.Schema(base_schema)
|
||||||
|
|
||||||
|
|
||||||
async def validate_input(hass: core.HomeAssistant, data):
|
async def validate_input(hass: core.HomeAssistant, data):
|
||||||
"""
|
"""
|
||||||
Validate the user input allows us to connect.
|
Validate the user input allows us to connect.
|
||||||
|
|
||||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
Data has the keys from _base_schema() with values provided by the user.
|
||||||
"""
|
"""
|
||||||
websession = async_get_clientsession(hass, verify_ssl=False)
|
websession = async_get_clientsession(hass, verify_ssl=False)
|
||||||
api = Smile(
|
api = Smile(
|
||||||
host=data["host"], password=data["password"], timeout=30, websession=websession
|
host=data[CONF_HOST],
|
||||||
|
password=data[CONF_PASSWORD],
|
||||||
|
timeout=30,
|
||||||
|
websession=websession,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await api.connect()
|
await api.connect()
|
||||||
except Smile.InvalidAuthentication:
|
except Smile.InvalidAuthentication:
|
||||||
raise InvalidAuth
|
raise InvalidAuth
|
||||||
except Smile.ConnectionFailedError:
|
except Smile.PlugwiseError:
|
||||||
raise CannotConnect
|
raise CannotConnect
|
||||||
|
|
||||||
return api
|
return api
|
||||||
|
@ -44,12 +62,39 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Plugwise config flow."""
|
||||||
|
self.discovery_info = {}
|
||||||
|
|
||||||
|
async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType):
|
||||||
|
"""Prepare configuration for a discovered Plugwise Smile."""
|
||||||
|
self.discovery_info = discovery_info
|
||||||
|
_properties = self.discovery_info.get("properties")
|
||||||
|
|
||||||
|
unique_id = self.discovery_info.get("hostname").split(".")[0]
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
_product = _properties.get("product", None)
|
||||||
|
_version = _properties.get("version", "n/a")
|
||||||
|
_name = f"{ZEROCONF_MAP.get(_product,_product)} v{_version}"
|
||||||
|
|
||||||
|
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||||
|
self.context["title_placeholders"] = {
|
||||||
|
CONF_HOST: discovery_info[CONF_HOST],
|
||||||
|
"name": _name,
|
||||||
|
}
|
||||||
|
return await self.async_step_user()
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
|
|
||||||
|
if self.discovery_info:
|
||||||
|
user_input[CONF_HOST] = self.discovery_info[CONF_HOST]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = await validate_input(self.hass, user_input)
|
api = await validate_input(self.hass, user_input)
|
||||||
|
|
||||||
|
@ -64,12 +109,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
await self.async_set_unique_id(api.gateway_id)
|
await self.async_set_unique_id(api.gateway_id)
|
||||||
self._abort_if_unique_id_configured()
|
|
||||||
|
|
||||||
return self.async_create_entry(title=api.smile_name, data=user_input)
|
return self.async_create_entry(title=api.smile_name, data=user_input)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
step_id="user", data_schema=_base_schema(self.discovery_info), errors=errors
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/plugwise",
|
"documentation": "https://www.home-assistant.io/integrations/plugwise",
|
||||||
"requirements": ["Plugwise_Smile==1.1.0"],
|
"requirements": ["Plugwise_Smile==1.1.0"],
|
||||||
"codeowners": ["@CoMPaTech", "@bouwew"],
|
"codeowners": ["@CoMPaTech", "@bouwew"],
|
||||||
|
"zeroconf": ["_plugwise._tcp.local."],
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "This Smile is already configured"
|
"already_configured": "This Smile is already configured"
|
||||||
}
|
},
|
||||||
|
"flow_title": "Smile: {name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,9 @@ ZEROCONF = {
|
||||||
"_nut._tcp.local.": [
|
"_nut._tcp.local.": [
|
||||||
"nut"
|
"nut"
|
||||||
],
|
],
|
||||||
|
"_plugwise._tcp.local.": [
|
||||||
|
"plugwise"
|
||||||
|
],
|
||||||
"_printer._tcp.local.": [
|
"_printer._tcp.local.": [
|
||||||
"brother"
|
"brother"
|
||||||
],
|
],
|
||||||
|
|
|
@ -12,6 +12,7 @@ from tests.async_mock import patch
|
||||||
def mock_smile():
|
def mock_smile():
|
||||||
"""Create a Mock Smile for testing exceptions."""
|
"""Create a Mock Smile for testing exceptions."""
|
||||||
with patch("homeassistant.components.plugwise.config_flow.Smile",) as smile_mock:
|
with patch("homeassistant.components.plugwise.config_flow.Smile",) as smile_mock:
|
||||||
|
smile_mock.PlugwiseError = Smile.PlugwiseError
|
||||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||||
smile_mock.return_value.connect.return_value = True
|
smile_mock.return_value.connect.return_value = True
|
||||||
|
|
Loading…
Reference in New Issue