mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 21:06:19 +00:00
Disable creating port mappings from UI, add discovery from component (#18565)
* Disable creating port mappings from UI, add discovery from component * Remove unused constant * Upgrade to async_upnp_client==0.13.6 and use manufacturer from device * Upgrade to async_upnp_client==0.13.7
This commit is contained in:
committed by
Diogo Gomes
parent
5efc61feaf
commit
501b3f9927
@@ -4,32 +4,31 @@ Will open a port in your router for Home Assistant and provide statistics.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/upnp/
|
||||
"""
|
||||
import asyncio
|
||||
from ipaddress import ip_address
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.helpers import config_entry_flow
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers import dispatcher
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.util import get_local_ip
|
||||
|
||||
from .const import (
|
||||
CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS,
|
||||
CONF_HASS, CONF_LOCAL_IP, CONF_PORTS,
|
||||
CONF_UDN, CONF_SSDP_DESCRIPTION,
|
||||
SIGNAL_REMOVE_SENSOR,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
from .const import LOGGER as _LOGGER
|
||||
from .config_flow import async_ensure_domain_data
|
||||
from .device import Device
|
||||
|
||||
|
||||
REQUIREMENTS = ['async-upnp-client==0.13.2']
|
||||
REQUIREMENTS = ['async-upnp-client==0.13.7']
|
||||
|
||||
NOTIFICATION_ID = 'upnp_notification'
|
||||
NOTIFICATION_TITLE = 'UPnP/IGD Setup'
|
||||
@@ -83,78 +82,111 @@ def _substitute_hass_ports(ports, hass_port=None):
|
||||
return ports
|
||||
|
||||
|
||||
# config
|
||||
async def async_discover_and_construct(hass, udn=None) -> Device:
|
||||
"""Discovery devices and construct a Device for one."""
|
||||
discovery_infos = await Device.async_discover(hass)
|
||||
if not discovery_infos:
|
||||
_LOGGER.info('No UPnP/IGD devices discovered')
|
||||
return None
|
||||
|
||||
if udn:
|
||||
# get the discovery info with specified UDN
|
||||
filtered = [di for di in discovery_infos if di['udn'] == udn]
|
||||
if not filtered:
|
||||
_LOGGER.warning('Wanted UPnP/IGD device with UDN "%s" not found, '
|
||||
'aborting', udn)
|
||||
return None
|
||||
discovery_info = filtered[0]
|
||||
else:
|
||||
# get the first/any
|
||||
discovery_info = discovery_infos[0]
|
||||
if len(discovery_infos) > 1:
|
||||
_LOGGER.info('Detected multiple UPnP/IGD devices, using: %s',
|
||||
discovery_info['igd_name'])
|
||||
|
||||
ssdp_description = discovery_info['ssdp_description']
|
||||
return await Device.async_create_device(hass, ssdp_description)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
"""Register a port mapping for Home Assistant via UPnP."""
|
||||
await async_ensure_domain_data(hass)
|
||||
|
||||
# ensure sane config
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
upnp_config = config[DOMAIN]
|
||||
|
||||
# overridden local ip
|
||||
if CONF_LOCAL_IP in upnp_config:
|
||||
hass.data[DOMAIN]['local_ip'] = upnp_config[CONF_LOCAL_IP]
|
||||
|
||||
# determine ports
|
||||
ports = {CONF_HASS: CONF_HASS} # default, port_mapping disabled by default
|
||||
if CONF_PORTS in upnp_config:
|
||||
# copy from config
|
||||
ports = upnp_config[CONF_PORTS]
|
||||
|
||||
hass.data[DOMAIN]['auto_config'] = {
|
||||
'active': True,
|
||||
'enable_sensors': upnp_config[CONF_ENABLE_SENSORS],
|
||||
'enable_port_mapping': upnp_config[CONF_ENABLE_PORT_MAPPING],
|
||||
'ports': ports,
|
||||
"""Set up UPnP component."""
|
||||
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] = {
|
||||
'config': conf,
|
||||
'devices': {},
|
||||
'local_ip': config.get(CONF_LOCAL_IP, local_ip),
|
||||
'ports': conf.get('ports', {}),
|
||||
}
|
||||
|
||||
if conf is not None:
|
||||
hass.async_create_task(hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': config_entries.SOURCE_IMPORT}))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# config flow
|
||||
async def async_setup_entry(hass: HomeAssistantType,
|
||||
config_entry: ConfigEntry):
|
||||
"""Set up UPnP/IGD-device from a config entry."""
|
||||
await async_ensure_domain_data(hass)
|
||||
data = config_entry.data
|
||||
"""Set up UPnP/IGD device from a config entry."""
|
||||
domain_data = hass.data[DOMAIN]
|
||||
conf = domain_data['config']
|
||||
|
||||
# build UPnP/IGD device
|
||||
ssdp_description = data[CONF_SSDP_DESCRIPTION]
|
||||
try:
|
||||
device = await Device.async_create_device(hass, ssdp_description)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
_LOGGER.error('Unable to create upnp-device')
|
||||
# discover and construct
|
||||
device = await async_discover_and_construct(hass,
|
||||
config_entry.data.get('udn'))
|
||||
if not device:
|
||||
_LOGGER.info('Unable to create UPnP/IGD, aborting')
|
||||
return False
|
||||
|
||||
# 'register'/save UDN
|
||||
config_entry.data['udn'] = device.udn
|
||||
hass.data[DOMAIN]['devices'][device.udn] = device
|
||||
hass.config_entries.async_update_entry(entry=config_entry,
|
||||
data=config_entry.data)
|
||||
|
||||
# port mapping
|
||||
if data.get(CONF_ENABLE_PORT_MAPPING):
|
||||
local_ip = hass.data[DOMAIN]['local_ip']
|
||||
ports = hass.data[DOMAIN]['auto_config']['ports']
|
||||
_LOGGER.debug('Enabling port mappings: %s', ports)
|
||||
# create device registry entry
|
||||
device_registry = await dr.async_get_registry(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={
|
||||
(dr.CONNECTION_UPNP, device.udn)
|
||||
},
|
||||
identifiers={
|
||||
(DOMAIN, device.udn)
|
||||
},
|
||||
name=device.name,
|
||||
manufacturer=device.manufacturer,
|
||||
)
|
||||
|
||||
hass_port = None
|
||||
if hasattr(hass, 'http'):
|
||||
hass_port = hass.http.server_port
|
||||
ports = _substitute_hass_ports(ports, hass_port=hass_port)
|
||||
await device.async_add_port_mappings(ports, local_ip)
|
||||
|
||||
# sensors
|
||||
if data.get(CONF_ENABLE_SENSORS):
|
||||
# set up sensors
|
||||
if conf.get(CONF_ENABLE_SENSORS):
|
||||
_LOGGER.debug('Enabling sensors')
|
||||
|
||||
# register sensor setup handlers
|
||||
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, 'sensor'))
|
||||
|
||||
# set up port mapping
|
||||
if conf.get(CONF_ENABLE_PORT_MAPPING):
|
||||
_LOGGER.debug('Enabling port mapping')
|
||||
local_ip = domain_data['local_ip']
|
||||
ports = conf.get('ports', {})
|
||||
|
||||
hass_port = None
|
||||
if hasattr(hass, 'http'):
|
||||
hass_port = hass.http.server_port
|
||||
|
||||
ports = _substitute_hass_ports(ports, hass_port=hass_port)
|
||||
await device.async_add_port_mappings(ports, local_ip)
|
||||
|
||||
# set up port mapping deletion on stop-hook
|
||||
async def delete_port_mapping(event):
|
||||
"""Delete port mapping on quit."""
|
||||
if data.get(CONF_ENABLE_PORT_MAPPING):
|
||||
_LOGGER.debug('Deleting port mappings')
|
||||
await device.async_delete_port_mappings()
|
||||
_LOGGER.debug('Deleting port mappings')
|
||||
await device.async_delete_port_mappings()
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, delete_port_mapping)
|
||||
|
||||
return True
|
||||
@@ -162,25 +194,23 @@ async def async_setup_entry(hass: HomeAssistantType,
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistantType,
|
||||
config_entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
data = config_entry.data
|
||||
udn = data[CONF_UDN]
|
||||
|
||||
if udn not in hass.data[DOMAIN]['devices']:
|
||||
return True
|
||||
"""Unload a UPnP/IGD device from a config entry."""
|
||||
udn = config_entry.data['udn']
|
||||
device = hass.data[DOMAIN]['devices'][udn]
|
||||
|
||||
# port mapping
|
||||
if data.get(CONF_ENABLE_PORT_MAPPING):
|
||||
_LOGGER.debug('Deleting port mappings')
|
||||
await device.async_delete_port_mappings()
|
||||
# remove port mapping
|
||||
_LOGGER.debug('Deleting port mappings')
|
||||
await device.async_delete_port_mappings()
|
||||
|
||||
# sensors
|
||||
if data.get(CONF_ENABLE_SENSORS):
|
||||
_LOGGER.debug('Deleting sensors')
|
||||
dispatcher.async_dispatcher_send(hass, SIGNAL_REMOVE_SENSOR, device)
|
||||
|
||||
# clear stored device
|
||||
del hass.data[DOMAIN]['devices'][udn]
|
||||
# remove sensors
|
||||
_LOGGER.debug('Deleting sensors')
|
||||
dispatcher.async_dispatcher_send(hass, SIGNAL_REMOVE_SENSOR, device)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
config_entry_flow.register_discovery_flow(
|
||||
DOMAIN,
|
||||
'UPnP/IGD',
|
||||
Device.async_discover,
|
||||
config_entries.CONN_CLASS_LOCAL_POLL)
|
||||
|
||||
Reference in New Issue
Block a user