core/homeassistant/components/upnp/device.py

175 lines
5.1 KiB
Python
Raw Normal View History

"""Home Assistant representation of an UPnP/IGD."""
2021-01-29 09:23:34 +00:00
from __future__ import annotations
2018-09-07 22:11:23 +00:00
import asyncio
from collections.abc import Mapping
2018-09-07 22:11:23 +00:00
from ipaddress import IPv4Address
2021-04-25 19:36:21 +00:00
from typing import Any
from urllib.parse import urlparse
2018-09-07 22:11:23 +00:00
from async_upnp_client import UpnpFactory
from async_upnp_client.aiohttp import AiohttpSessionRequester
from async_upnp_client.device_updater import DeviceUpdater
from async_upnp_client.profiles.igd import IgdDevice
2018-09-07 22:11:23 +00:00
from homeassistant.core import HomeAssistant
2018-09-07 22:11:23 +00:00
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
import homeassistant.util.dt as dt_util
2018-09-07 22:11:23 +00:00
from .const import (
BYTES_RECEIVED,
BYTES_SENT,
CONF_LOCAL_IP,
DOMAIN,
DOMAIN_CONFIG,
LOGGER as _LOGGER,
PACKETS_RECEIVED,
PACKETS_SENT,
TIMESTAMP,
Add upnp binary sensor for connectivity status (#54489) * New binary sensor for connectivity * Add binary_sensor * New binary sensor for connectivity * Add binary_sensor * Handle values returned as None * Small text update for Uptime * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Updates based on review * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Further updates based on review * Set device_class as a class atribute * Create 1 combined data coordinator and UpnpEntity class * Updates on coordinator * Update comment * Fix in async_step_init for coordinator * Add async_get_status to mocked device and set times polled for each call seperately * Updated to get device through coordinator Check polling for each status call seperately * Use collections.abc instead of Typing for Mapping * Remove adding device to hass.data as coordinator is now saved * Removed setting _coordinator * Added myself as codeowner * Update type in __init__ * Removed attributes from binary sensor * Fix async_unload_entry * Add expected return value to is_on Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-08-17 18:23:41 +00:00
UPTIME,
WANIP,
WANSTATUS,
)
2018-09-07 22:11:23 +00:00
def _get_local_ip(hass: HomeAssistant) -> IPv4Address | None:
"""Get the configured local ip."""
if DOMAIN in hass.data and DOMAIN_CONFIG in hass.data[DOMAIN]:
local_ip = hass.data[DOMAIN][DOMAIN_CONFIG].get(CONF_LOCAL_IP)
if local_ip:
return IPv4Address(local_ip)
return None
2018-09-07 22:11:23 +00:00
class Device:
2021-01-29 09:23:34 +00:00
"""Home Assistant representation of a UPnP/IGD device."""
2018-09-07 22:11:23 +00:00
def __init__(self, igd_device: IgdDevice, device_updater: DeviceUpdater) -> None:
"""Initialize UPnP/IGD device."""
self._igd_device = igd_device
self._device_updater = device_updater
self.coordinator: DataUpdateCoordinator = None
2018-09-07 22:11:23 +00:00
@classmethod
2021-01-29 09:23:34 +00:00
async def async_create_device(
cls, hass: HomeAssistant, ssdp_location: str
2021-01-29 09:23:34 +00:00
) -> Device:
2018-09-17 20:08:09 +00:00
"""Create UPnP/IGD device."""
# Build async_upnp_client requester.
2018-09-07 22:11:23 +00:00
session = async_get_clientsession(hass)
requester = AiohttpSessionRequester(session, True, 10)
2018-09-07 22:11:23 +00:00
# Create async_upnp_client device.
2019-07-31 19:25:30 +00:00
factory = UpnpFactory(requester, disable_state_variable_validation=True)
upnp_device = await factory.async_create_device(ssdp_location)
2018-09-07 22:11:23 +00:00
# Create profile wrapper.
2018-09-07 22:11:23 +00:00
igd_device = IgdDevice(upnp_device, None)
# Create updater.
local_ip = _get_local_ip(hass)
device_updater = DeviceUpdater(
device=upnp_device, factory=factory, source_ip=local_ip
)
return cls(igd_device, device_updater)
async def async_start(self) -> None:
"""Start the device updater."""
await self._device_updater.async_start()
async def async_stop(self) -> None:
"""Stop the device updater."""
await self._device_updater.async_stop()
2018-09-07 22:11:23 +00:00
@property
def udn(self) -> str:
2018-09-07 22:11:23 +00:00
"""Get the UDN."""
return self._igd_device.udn
@property
def name(self) -> str:
2018-09-07 22:11:23 +00:00
"""Get the name."""
return self._igd_device.name
@property
def manufacturer(self) -> str:
"""Get the manufacturer."""
return self._igd_device.manufacturer
@property
def model_name(self) -> str:
"""Get the model name."""
return self._igd_device.model_name
@property
def device_type(self) -> str:
"""Get the device type."""
return self._igd_device.device_type
2021-01-29 09:23:34 +00:00
@property
def usn(self) -> str:
"""Get the USN."""
return f"{self.udn}::{self.device_type}"
@property
def unique_id(self) -> str:
"""Get the unique id."""
2021-01-29 09:23:34 +00:00
return self.usn
@property
def hostname(self) -> str:
"""Get the hostname."""
url = self._igd_device.device.device_url
parsed = urlparse(url)
return parsed.hostname
def __str__(self) -> str:
"""Get string representation."""
return f"IGD Device: {self.name}/{self.udn}::{self.device_type}"
2021-04-25 19:36:21 +00:00
async def async_get_traffic_data(self) -> Mapping[str, Any]:
"""
Get all traffic data in one go.
Traffic data consists of:
- total bytes sent
- total bytes received
- total packets sent
- total packats received
Data is timestamped.
"""
_LOGGER.debug("Getting traffic statistics from device: %s", self)
values = await asyncio.gather(
self._igd_device.async_get_total_bytes_received(),
self._igd_device.async_get_total_bytes_sent(),
self._igd_device.async_get_total_packets_received(),
self._igd_device.async_get_total_packets_sent(),
)
2018-09-07 22:11:23 +00:00
return {
TIMESTAMP: dt_util.utcnow(),
BYTES_RECEIVED: values[0],
BYTES_SENT: values[1],
PACKETS_RECEIVED: values[2],
PACKETS_SENT: values[3],
}
Add upnp binary sensor for connectivity status (#54489) * New binary sensor for connectivity * Add binary_sensor * New binary sensor for connectivity * Add binary_sensor * Handle values returned as None * Small text update for Uptime * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Updates based on review * Update homeassistant/components/upnp/binary_sensor.py Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * Further updates based on review * Set device_class as a class atribute * Create 1 combined data coordinator and UpnpEntity class * Updates on coordinator * Update comment * Fix in async_step_init for coordinator * Add async_get_status to mocked device and set times polled for each call seperately * Updated to get device through coordinator Check polling for each status call seperately * Use collections.abc instead of Typing for Mapping * Remove adding device to hass.data as coordinator is now saved * Removed setting _coordinator * Added myself as codeowner * Update type in __init__ * Removed attributes from binary sensor * Fix async_unload_entry * Add expected return value to is_on Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-08-17 18:23:41 +00:00
async def async_get_status(self) -> Mapping[str, Any]:
"""Get connection status, uptime, and external IP."""
_LOGGER.debug("Getting status for device: %s", self)
values = await asyncio.gather(
self._igd_device.async_get_status_info(),
self._igd_device.async_get_external_ip_address(),
)
return {
WANSTATUS: values[0][0] if values[0] is not None else None,
UPTIME: values[0][2] if values[0] is not None else None,
WANIP: values[1],
}