2019-02-13 20:21:14 +00:00
|
|
|
"""Open ports in your router for Home Assistant and provide statistics."""
|
2020-08-13 10:11:58 +00:00
|
|
|
import asyncio
|
2018-08-17 19:28:29 +00:00
|
|
|
from ipaddress import ip_address
|
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-12-21 17:25:23 +00:00
|
|
|
from homeassistant import config_entries
|
2018-08-29 19:19:04 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
2020-04-10 22:24:03 +00:00
|
|
|
from homeassistant.exceptions import ConfigEntryNotReady
|
|
|
|
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
2019-11-25 23:37:03 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
2018-12-21 17:25:23 +00:00
|
|
|
from homeassistant.util import get_local_ip
|
2018-08-17 19:28:29 +00:00
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
from .const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_LOCAL_IP,
|
2021-02-21 02:26:17 +00:00
|
|
|
CONFIG_ENTRY_HOSTNAME,
|
2020-05-03 01:03:54 +00:00
|
|
|
CONFIG_ENTRY_ST,
|
|
|
|
CONFIG_ENTRY_UDN,
|
|
|
|
DISCOVERY_LOCATION,
|
|
|
|
DISCOVERY_ST,
|
|
|
|
DISCOVERY_UDN,
|
2019-11-25 23:37:03 +00:00
|
|
|
DOMAIN,
|
2020-08-05 00:24:42 +00:00
|
|
|
DOMAIN_CONFIG,
|
|
|
|
DOMAIN_COORDINATORS,
|
|
|
|
DOMAIN_DEVICES,
|
|
|
|
DOMAIN_LOCAL_IP,
|
2019-11-25 23:37:03 +00:00
|
|
|
LOGGER as _LOGGER,
|
2018-09-01 19:20:15 +00:00
|
|
|
)
|
2018-09-07 22:11:23 +00:00
|
|
|
from .device import Device
|
2018-08-17 19:28:29 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
NOTIFICATION_ID = "upnp_notification"
|
|
|
|
NOTIFICATION_TITLE = "UPnP/IGD Setup"
|
|
|
|
|
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
2021-02-21 02:26:17 +00:00
|
|
|
{
|
|
|
|
DOMAIN: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
2019-07-31 19:25:30 +00:00
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
2018-08-17 19:28:29 +00:00
|
|
|
|
|
|
|
|
2021-01-29 09:23:34 +00:00
|
|
|
async def async_construct_device(hass: HomeAssistantType, udn: str, st: str) -> Device:
|
2018-12-21 17:25:23 +00:00
|
|
|
"""Discovery devices and construct a Device for one."""
|
2020-04-10 22:24:03 +00:00
|
|
|
# pylint: disable=invalid-name
|
2020-12-12 18:47:46 +00:00
|
|
|
_LOGGER.debug("Constructing device: %s::%s", udn, st)
|
|
|
|
|
2021-01-29 09:23:34 +00:00
|
|
|
discoveries = [
|
|
|
|
discovery
|
|
|
|
for discovery in await Device.async_discover(hass)
|
|
|
|
if discovery[DISCOVERY_UDN] == udn and discovery[DISCOVERY_ST] == st
|
|
|
|
]
|
|
|
|
if not discoveries:
|
|
|
|
_LOGGER.info("Device not discovered")
|
2018-12-21 17:25:23 +00:00
|
|
|
return None
|
|
|
|
|
2021-01-29 09:23:34 +00:00
|
|
|
# Some additional clues for remote debugging.
|
|
|
|
if len(discoveries) > 1:
|
|
|
|
_LOGGER.info("Multiple devices discovered: %s", discoveries)
|
2018-12-21 17:25:23 +00:00
|
|
|
|
2021-01-29 09:23:34 +00:00
|
|
|
discovery = discoveries[0]
|
|
|
|
_LOGGER.debug("Constructing from discovery: %s", discovery)
|
|
|
|
location = discovery[DISCOVERY_LOCATION]
|
2020-05-03 01:03:54 +00:00
|
|
|
return await Device.async_create_device(hass, location)
|
2018-12-21 17:25:23 +00:00
|
|
|
|
|
|
|
|
2018-08-30 14:38:43 +00:00
|
|
|
async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
2018-12-21 17:25:23 +00:00
|
|
|
"""Set up UPnP component."""
|
2020-05-03 01:03:54 +00:00
|
|
|
_LOGGER.debug("async_setup, config: %s", config)
|
2018-12-21 17:25:23 +00:00
|
|
|
conf_default = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN]
|
|
|
|
conf = config.get(DOMAIN, conf_default)
|
|
|
|
local_ip = await hass.async_add_executor_job(get_local_ip)
|
|
|
|
hass.data[DOMAIN] = {
|
2020-08-05 00:24:42 +00:00
|
|
|
DOMAIN_CONFIG: conf,
|
|
|
|
DOMAIN_COORDINATORS: {},
|
|
|
|
DOMAIN_DEVICES: {},
|
|
|
|
DOMAIN_LOCAL_IP: conf.get(CONF_LOCAL_IP, local_ip),
|
2018-08-30 14:38:43 +00:00
|
|
|
}
|
2018-08-17 19:28:29 +00:00
|
|
|
|
2020-05-03 01:03:54 +00:00
|
|
|
# Only start if set up via configuration.yaml.
|
|
|
|
if DOMAIN in config:
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
|
|
|
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}
|
|
|
|
)
|
|
|
|
)
|
2018-12-21 17:25:23 +00:00
|
|
|
|
2018-08-17 19:28:29 +00:00
|
|
|
return True
|
|
|
|
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2020-04-10 22:24:03 +00:00
|
|
|
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool:
|
2018-12-21 17:25:23 +00:00
|
|
|
"""Set up UPnP/IGD device from a config entry."""
|
2020-12-12 18:47:46 +00:00
|
|
|
_LOGGER.debug("Setting up config entry: %s", config_entry.unique_id)
|
2018-12-21 17:25:23 +00:00
|
|
|
|
2020-10-06 11:57:36 +00:00
|
|
|
# Discover and construct.
|
2021-01-29 09:23:34 +00:00
|
|
|
udn = config_entry.data[CONFIG_ENTRY_UDN]
|
|
|
|
st = config_entry.data[CONFIG_ENTRY_ST] # pylint: disable=invalid-name
|
2020-08-13 10:11:58 +00:00
|
|
|
try:
|
2021-01-29 09:23:34 +00:00
|
|
|
device = await async_construct_device(hass, udn, st)
|
2020-08-28 11:50:32 +00:00
|
|
|
except asyncio.TimeoutError as err:
|
|
|
|
raise ConfigEntryNotReady from err
|
2020-08-13 10:11:58 +00:00
|
|
|
|
2018-12-21 17:25:23 +00:00
|
|
|
if not device:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.info("Unable to create UPnP/IGD, aborting")
|
2020-04-10 22:24:03 +00:00
|
|
|
raise ConfigEntryNotReady
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2020-10-06 11:57:36 +00:00
|
|
|
# Save device.
|
2020-08-05 00:24:42 +00:00
|
|
|
hass.data[DOMAIN][DOMAIN_DEVICES][device.udn] = device
|
2020-05-03 01:03:54 +00:00
|
|
|
|
2020-10-06 11:57:36 +00:00
|
|
|
# Ensure entry has a unique_id.
|
|
|
|
if not config_entry.unique_id:
|
2020-12-12 18:47:46 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"Setting unique_id: %s, for config_entry: %s",
|
|
|
|
device.unique_id,
|
|
|
|
config_entry,
|
|
|
|
)
|
2020-05-03 01:03:54 +00:00
|
|
|
hass.config_entries.async_update_entry(
|
2020-08-27 11:56:20 +00:00
|
|
|
entry=config_entry,
|
|
|
|
unique_id=device.unique_id,
|
2020-05-03 01:03:54 +00:00
|
|
|
)
|
2018-12-21 17:25:23 +00:00
|
|
|
|
2021-02-21 02:26:17 +00:00
|
|
|
# Ensure entry has a hostname, for older entries.
|
2021-02-24 03:00:47 +00:00
|
|
|
if (
|
|
|
|
CONFIG_ENTRY_HOSTNAME not in config_entry.data
|
|
|
|
or config_entry.data[CONFIG_ENTRY_HOSTNAME] != device.hostname
|
|
|
|
):
|
2021-02-21 02:26:17 +00:00
|
|
|
hass.config_entries.async_update_entry(
|
|
|
|
entry=config_entry,
|
|
|
|
data={CONFIG_ENTRY_HOSTNAME: device.hostname, **config_entry.data},
|
|
|
|
)
|
|
|
|
|
2020-05-04 17:30:43 +00:00
|
|
|
# Create device registry entry.
|
2018-12-21 17:25:23 +00:00
|
|
|
device_registry = await dr.async_get_registry(hass)
|
|
|
|
device_registry.async_get_or_create(
|
|
|
|
config_entry_id=config_entry.entry_id,
|
2019-07-31 19:25:30 +00:00
|
|
|
connections={(dr.CONNECTION_UPNP, device.udn)},
|
|
|
|
identifiers={(DOMAIN, device.udn)},
|
2018-12-21 17:25:23 +00:00
|
|
|
name=device.name,
|
|
|
|
manufacturer=device.manufacturer,
|
2019-10-31 19:51:35 +00:00
|
|
|
model=device.model_name,
|
2018-12-21 17:25:23 +00:00
|
|
|
)
|
|
|
|
|
2020-05-04 17:30:43 +00:00
|
|
|
# Create sensors.
|
|
|
|
_LOGGER.debug("Enabling sensors")
|
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
|
|
|
|
)
|
2018-08-17 19:28:29 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2020-04-10 22:24:03 +00:00
|
|
|
async def async_unload_entry(
|
|
|
|
hass: HomeAssistantType, config_entry: ConfigEntry
|
|
|
|
) -> bool:
|
2018-12-21 17:25:23 +00:00
|
|
|
"""Unload a UPnP/IGD device from a config entry."""
|
2020-12-12 18:47:46 +00:00
|
|
|
_LOGGER.debug("Unloading config entry: %s", config_entry.unique_id)
|
|
|
|
|
2020-05-11 18:03:12 +00:00
|
|
|
udn = config_entry.data.get(CONFIG_ENTRY_UDN)
|
2020-08-05 00:24:42 +00:00
|
|
|
if udn in hass.data[DOMAIN][DOMAIN_DEVICES]:
|
|
|
|
del hass.data[DOMAIN][DOMAIN_DEVICES][udn]
|
|
|
|
if udn in hass.data[DOMAIN][DOMAIN_COORDINATORS]:
|
|
|
|
del hass.data[DOMAIN][DOMAIN_COORDINATORS][udn]
|
2020-05-11 18:03:12 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug("Deleting sensors")
|
2020-04-10 22:24:03 +00:00
|
|
|
return await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|