Add SSDP discovery to unifi (#45364)
parent
ded242a8fe
commit
b68c287ff1
|
@ -1,9 +1,11 @@
|
|||
"""Config flow for UniFi."""
|
||||
import socket
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import ssdp
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
|
@ -13,6 +15,7 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
from .const import (
|
||||
CONF_ALLOW_BANDWIDTH_SENSORS,
|
||||
|
@ -42,6 +45,12 @@ DEFAULT_SITE_ID = "default"
|
|||
DEFAULT_VERIFY_SSL = False
|
||||
|
||||
|
||||
MODEL_PORTS = {
|
||||
"UniFi Dream Machine": 443,
|
||||
"UniFi Dream Machine Pro": 443,
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def get_controller_id_from_config_entry(config_entry):
|
||||
"""Return controller with a matching bridge id."""
|
||||
|
@ -65,7 +74,7 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN):
|
|||
|
||||
def __init__(self):
|
||||
"""Initialize the UniFi flow."""
|
||||
self.config = None
|
||||
self.config = {}
|
||||
self.sites = None
|
||||
self.reauth_config_entry = {}
|
||||
self.reauth_config = {}
|
||||
|
@ -112,15 +121,17 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN):
|
|||
)
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
host = ""
|
||||
if await async_discover_unifi(self.hass):
|
||||
host = self.config.get(CONF_HOST)
|
||||
if not host and await async_discover_unifi(self.hass):
|
||||
host = "unifi"
|
||||
|
||||
data = self.reauth_schema or {
|
||||
vol.Required(CONF_HOST, default=host): str,
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
vol.Optional(
|
||||
CONF_PORT, default=self.config.get(CONF_PORT, DEFAULT_PORT)
|
||||
): int,
|
||||
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
|
||||
}
|
||||
|
||||
|
@ -194,6 +205,43 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN):
|
|||
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_ssdp(self, discovery_info):
|
||||
"""Handle a discovered unifi device."""
|
||||
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
|
||||
model_description = discovery_info[ssdp.ATTR_UPNP_MODEL_DESCRIPTION]
|
||||
mac_address = format_mac(discovery_info[ssdp.ATTR_UPNP_SERIAL])
|
||||
|
||||
self.config = {
|
||||
CONF_HOST: parsed_url.hostname,
|
||||
}
|
||||
|
||||
if self._host_already_configured(self.config[CONF_HOST]):
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
await self.async_set_unique_id(mac_address)
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: self.config[CONF_HOST]})
|
||||
|
||||
# pylint: disable=no-member
|
||||
self.context["title_placeholders"] = {
|
||||
CONF_HOST: self.config[CONF_HOST],
|
||||
CONF_SITE_ID: "default",
|
||||
}
|
||||
|
||||
port = MODEL_PORTS.get(model_description)
|
||||
if port is not None:
|
||||
self.config[CONF_PORT] = port
|
||||
|
||||
return await self.async_step_user()
|
||||
|
||||
def _host_already_configured(self, host):
|
||||
"""See if we already have a unifi entry matching the host."""
|
||||
for entry in self._async_current_entries():
|
||||
if not entry.data:
|
||||
continue
|
||||
if entry.data[CONF_CONTROLLER][CONF_HOST] == host:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class UnifiOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle Unifi options."""
|
||||
|
|
|
@ -5,5 +5,17 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/unifi",
|
||||
"requirements": ["aiounifi==26"],
|
||||
"codeowners": ["@Kane610"],
|
||||
"quality_scale": "platinum"
|
||||
"quality_scale": "platinum",
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
"deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
|
||||
"modelDescription": "UniFi Dream Machine"
|
||||
},
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
"deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
|
||||
"modelDescription": "UniFi Dream Machine Pro"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"config": {
|
||||
"flow_title": "{site} ({host})",
|
||||
"flow_title": "UniFi Network {site} ({host})",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up UniFi Controller",
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Controller site is already configured",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
"reauth_successful": "Re-authentication was successful"
|
||||
},
|
||||
"error": {
|
||||
"faulty_credentials": "Invalid authentication",
|
||||
"service_unavailable": "Failed to connect",
|
||||
"unknown_client_mac": "No client available on that MAC address"
|
||||
},
|
||||
"flow_title": "{site} ({host})",
|
||||
"flow_title": "UniFi Network {site} ({host})",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
|
|
|
@ -166,6 +166,18 @@ SSDP = {
|
|||
"manufacturer": "Synology"
|
||||
}
|
||||
],
|
||||
"unifi": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
"modelDescription": "UniFi Dream Machine"
|
||||
},
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
"modelDescription": "UniFi Dream Machine Pro"
|
||||
}
|
||||
],
|
||||
"upnp": [
|
||||
{
|
||||
"st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
||||
|
|
|
@ -3,7 +3,7 @@ from unittest.mock import patch
|
|||
|
||||
import aiounifi
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant import config_entries, data_entry_flow, setup
|
||||
from homeassistant.components.unifi.const import (
|
||||
CONF_ALLOW_BANDWIDTH_SENSORS,
|
||||
CONF_ALLOW_UPTIME_SENSORS,
|
||||
|
@ -466,3 +466,109 @@ async def test_simple_option_flow(hass):
|
|||
CONF_TRACK_DEVICES: False,
|
||||
CONF_BLOCK_CLIENT: [CLIENTS[0]["mac"]],
|
||||
}
|
||||
|
||||
|
||||
async def test_form_ssdp(hass):
|
||||
"""Test we get the form with ssdp source."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
UNIFI_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data={
|
||||
"friendlyName": "UniFi Dream Machine",
|
||||
"modelDescription": "UniFi Dream Machine Pro",
|
||||
"ssdp_location": "http://192.168.208.1:41417/rootDesc.xml",
|
||||
"serialNumber": "e0:63:da:20:14:a9",
|
||||
},
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
context = next(
|
||||
flow["context"]
|
||||
for flow in hass.config_entries.flow.async_progress()
|
||||
if flow["flow_id"] == result["flow_id"]
|
||||
)
|
||||
assert context["title_placeholders"] == {
|
||||
"host": "192.168.208.1",
|
||||
"site": "default",
|
||||
}
|
||||
|
||||
|
||||
async def test_form_ssdp_aborts_if_host_already_exists(hass):
|
||||
"""Test we abort if the host is already configured."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=UNIFI_DOMAIN,
|
||||
data={"controller": {"host": "192.168.208.1", "site": "site_id"}},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
UNIFI_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data={
|
||||
"friendlyName": "UniFi Dream Machine",
|
||||
"modelDescription": "UniFi Dream Machine Pro",
|
||||
"ssdp_location": "http://192.168.208.1:41417/rootDesc.xml",
|
||||
"serialNumber": "e0:63:da:20:14:a9",
|
||||
},
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_form_ssdp_aborts_if_serial_already_exists(hass):
|
||||
"""Test we abort if the serial is already configured."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=UNIFI_DOMAIN,
|
||||
data={"controller": {"host": "1.2.3.4", "site": "site_id"}},
|
||||
unique_id="e0:63:da:20:14:a9",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
UNIFI_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data={
|
||||
"friendlyName": "UniFi Dream Machine",
|
||||
"modelDescription": "UniFi Dream Machine Pro",
|
||||
"ssdp_location": "http://192.168.208.1:41417/rootDesc.xml",
|
||||
"serialNumber": "e0:63:da:20:14:a9",
|
||||
},
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_form_ssdp_gets_form_with_ignored_entry(hass):
|
||||
"""Test we can still setup if there is an ignored entry."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=UNIFI_DOMAIN,
|
||||
data={},
|
||||
source=config_entries.SOURCE_IGNORE,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
UNIFI_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data={
|
||||
"friendlyName": "UniFi Dream Machine New",
|
||||
"modelDescription": "UniFi Dream Machine Pro",
|
||||
"ssdp_location": "http://1.2.3.4:41417/rootDesc.xml",
|
||||
"serialNumber": "e0:63:da:20:14:a9",
|
||||
},
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
context = next(
|
||||
flow["context"]
|
||||
for flow in hass.config_entries.flow.async_progress()
|
||||
if flow["flow_id"] == result["flow_id"]
|
||||
)
|
||||
assert context["title_placeholders"] == {
|
||||
"host": "1.2.3.4",
|
||||
"site": "default",
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue