core/homeassistant/components/upnp/device.py

159 lines
5.7 KiB
Python

"""Hass representation of an UPnP/IGD."""
import asyncio
from ipaddress import IPv4Address
import aiohttp
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import HomeAssistantType
from .const import LOGGER as _LOGGER
from .const import (DOMAIN, CONF_LOCAL_IP)
class Device:
"""Hass representation of an UPnP/IGD."""
def __init__(self, igd_device):
"""Initializer."""
self._igd_device = igd_device
self._mapped_ports = []
@classmethod
async def async_discover(cls, hass: HomeAssistantType):
"""Discovery UPNP/IGD devices."""
_LOGGER.debug('Discovering UPnP/IGD devices')
local_ip = hass.data[DOMAIN]['config'].get(CONF_LOCAL_IP)
if local_ip:
local_ip = IPv4Address(local_ip)
# discover devices
from async_upnp_client.profiles.igd import IgdDevice
discovery_infos = await IgdDevice.async_search(source_ip=local_ip)
# add extra info and store devices
devices = []
for discovery_info in discovery_infos:
discovery_info['udn'] = discovery_info['_udn']
discovery_info['ssdp_description'] = discovery_info['location']
discovery_info['source'] = 'async_upnp_client'
_LOGGER.debug('Discovered device: %s', discovery_info)
devices.append(discovery_info)
return devices
@classmethod
async def async_create_device(cls,
hass: HomeAssistantType,
ssdp_description: str):
"""Create UPnP/IGD device."""
# build async_upnp_client requester
from async_upnp_client.aiohttp import AiohttpSessionRequester
session = async_get_clientsession(hass)
requester = AiohttpSessionRequester(session, True)
# create async_upnp_client device
from async_upnp_client import UpnpFactory
factory = UpnpFactory(requester,
disable_state_variable_validation=True)
upnp_device = await factory.async_create_device(ssdp_description)
# wrap with async_upnp_client.IgdDevice
from async_upnp_client.profiles.igd import IgdDevice
igd_device = IgdDevice(upnp_device, None)
return cls(igd_device)
@property
def udn(self):
"""Get the UDN."""
return self._igd_device.udn
@property
def name(self):
"""Get the name."""
return self._igd_device.name
@property
def manufacturer(self):
"""Get the manufacturer."""
return self._igd_device.manufacturer
async def async_add_port_mappings(self, ports, local_ip):
"""Add port mappings."""
if local_ip == '127.0.0.1':
_LOGGER.error(
'Could not create port mapping, our IP is 127.0.0.1')
# determine local ip, ensure sane IP
local_ip = IPv4Address(local_ip)
# create port mappings
for external_port, internal_port in ports.items():
await self._async_add_port_mapping(external_port,
local_ip,
internal_port)
self._mapped_ports.append(external_port)
async def _async_add_port_mapping(self,
external_port,
local_ip,
internal_port):
"""Add a port mapping."""
# create port mapping
from async_upnp_client import UpnpError
_LOGGER.info('Creating port mapping %s:%s:%s (TCP)',
external_port, local_ip, internal_port)
try:
await self._igd_device.async_add_port_mapping(
remote_host=None,
external_port=external_port,
protocol='TCP',
internal_port=internal_port,
internal_client=local_ip,
enabled=True,
description="Home Assistant",
lease_duration=None)
self._mapped_ports.append(external_port)
except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError):
_LOGGER.error('Could not add port mapping: %s:%s:%s',
external_port, local_ip, internal_port)
async def async_delete_port_mappings(self):
"""Remove a port mapping."""
for port in self._mapped_ports:
await self._async_delete_port_mapping(port)
async def _async_delete_port_mapping(self, external_port):
"""Remove a port mapping."""
from async_upnp_client import UpnpError
_LOGGER.info('Deleting port mapping %s (TCP)', external_port)
try:
await self._igd_device.async_delete_port_mapping(
remote_host=None,
external_port=external_port,
protocol='TCP')
self._mapped_ports.remove(external_port)
except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError):
_LOGGER.error('Could not delete port mapping')
async def async_get_total_bytes_received(self):
"""Get total bytes received."""
return await self._igd_device.async_get_total_bytes_received()
async def async_get_total_bytes_sent(self):
"""Get total bytes sent."""
return await self._igd_device.async_get_total_bytes_sent()
async def async_get_total_packets_received(self):
"""Get total packets received."""
# pylint: disable=invalid-name
return await self._igd_device.async_get_total_packets_received()
async def async_get_total_packets_sent(self):
"""Get total packets sent."""
return await self._igd_device.async_get_total_packets_sent()