1 """Home Assistant representation of an UPnP/IGD."""
3 from __future__
import annotations
5 from datetime
import datetime
6 from functools
import partial
7 from ipaddress
import ip_address
9 from urllib.parse
import urlparse
11 from async_upnp_client.aiohttp
import AiohttpNotifyServer, AiohttpSessionRequester
12 from async_upnp_client.client_factory
import UpnpFactory
13 from async_upnp_client.const
import AddressTupleVXType
14 from async_upnp_client.exceptions
import UpnpCommunicationError
15 from async_upnp_client.profiles.igd
import IgdDevice, IgdStateItem
16 from async_upnp_client.utils
import async_get_local_ip
17 from getmac
import get_mac_address
26 KIBIBYTES_PER_SEC_RECEIVED,
27 KIBIBYTES_PER_SEC_SENT,
29 PACKETS_PER_SEC_RECEIVED,
33 PORT_MAPPING_NUMBER_OF_ENTRIES_IPV4,
40 TYPE_STATE_ITEM_MAPPING = {
41 BYTES_RECEIVED: IgdStateItem.BYTES_RECEIVED,
42 BYTES_SENT: IgdStateItem.BYTES_SENT,
43 KIBIBYTES_PER_SEC_RECEIVED: IgdStateItem.KIBIBYTES_PER_SEC_RECEIVED,
44 KIBIBYTES_PER_SEC_SENT: IgdStateItem.KIBIBYTES_PER_SEC_SENT,
45 PACKETS_PER_SEC_RECEIVED: IgdStateItem.PACKETS_PER_SEC_RECEIVED,
46 PACKETS_PER_SEC_SENT: IgdStateItem.PACKETS_PER_SEC_SENT,
47 PACKETS_RECEIVED: IgdStateItem.PACKETS_RECEIVED,
48 PACKETS_SENT: IgdStateItem.PACKETS_SENT,
49 ROUTER_IP: IgdStateItem.EXTERNAL_IP_ADDRESS,
50 ROUTER_UPTIME: IgdStateItem.UPTIME,
51 WAN_STATUS: IgdStateItem.CONNECTION_STATUS,
52 PORT_MAPPING_NUMBER_OF_ENTRIES_IPV4: IgdStateItem.PORT_MAPPING_NUMBER_OF_ENTRIES,
57 """Get the preferred location (an IPv4 location) from a set of locations."""
59 for location
in locations:
60 if location.startswith((
"http://[",
"https://[")):
66 for location
in locations:
69 raise ValueError(
"No location found")
73 """Get mac address from host."""
74 ip_addr = ip_address(host)
75 if ip_addr.version == 4:
76 mac_address = await hass.async_add_executor_job(
77 partial(get_mac_address, ip=host)
80 mac_address = await hass.async_add_executor_job(
81 partial(get_mac_address, ip6=host)
87 hass: HomeAssistant, location: str, force_poll: bool
89 """Create UPnP/IGD device."""
91 requester = AiohttpSessionRequester(session, with_sleep=
True, timeout=20)
94 factory = UpnpFactory(requester, non_strict=
True)
95 upnp_device = await factory.async_create_device(location)
98 _, local_ip = await async_get_local_ip(location)
99 source: AddressTupleVXType = (local_ip, 0)
100 notify_server = AiohttpNotifyServer(
104 await notify_server.async_start_server()
105 _LOGGER.debug(
"Started event handler at %s", notify_server.callback_url)
108 igd_device = IgdDevice(upnp_device, notify_server.event_handler)
109 return Device(hass, igd_device, force_poll)
113 """Home Assistant representation of a UPnP/IGD device."""
116 self, hass: HomeAssistant, igd_device: IgdDevice, force_poll: bool
118 """Initialize UPnP/IGD device."""
124 DataUpdateCoordinator[dict[str, str | datetime | int | float |
None]] |
None
126 self.original_udn: str |
None =
None
129 """Get mac address."""
130 if not self.
hosthost:
147 """Get the manufacturer."""
152 """Get the model name."""
157 """Get the device type."""
163 return f
"{self.udn}::{self.device_type}"
167 """Get the unique id."""
172 """Get the hostname."""
174 return parsed.hostname
178 """Get the device_url of the device."""
179 return self.
_igd_device_igd_device.device.device_url
183 """Get the serial number."""
184 return self.
_igd_device_igd_device.device.serial_number
187 """Get string representation."""
188 return f
"IGD Device: {self.name}/{self.udn}::{self.device_type}"
192 """Get force_poll."""
196 """Set force_poll, and (un)subscribe if needed."""
206 """Subscribe to services."""
209 except UpnpCommunicationError
as ex:
211 "Error subscribing to services, falling back to forced polling: %s", ex
216 """Unsubscribe from services."""
219 except UpnpCommunicationError
as ex:
220 _LOGGER.debug(
"Error unsubscribing to services: %s", ex)
223 self, entity_description_keys: list[str] |
None
224 ) -> dict[str, str | datetime | int | float |
None]:
225 """Get all data from device."""
226 if not entity_description_keys:
227 igd_state_items =
None
230 TYPE_STATE_ITEM_MAPPING[key]
for key
in entity_description_keys
234 "Getting data for device: %s, state_items: %s, force_poll: %s",
239 igd_state = await self.
_igd_device_igd_device.async_get_traffic_and_status_data(
240 igd_state_items, force_poll=self.
_force_poll_force_poll
244 if value
is None or isinstance(value, BaseException):
250 TIMESTAMP: igd_state.timestamp,
251 BYTES_RECEIVED:
get_value(igd_state.bytes_received),
252 BYTES_SENT:
get_value(igd_state.bytes_sent),
253 PACKETS_RECEIVED:
get_value(igd_state.packets_received),
254 PACKETS_SENT:
get_value(igd_state.packets_sent),
255 WAN_STATUS:
get_value(igd_state.connection_status),
256 ROUTER_UPTIME:
get_value(igd_state.uptime),
257 ROUTER_IP:
get_value(igd_state.external_ip_address),
258 KIBIBYTES_PER_SEC_RECEIVED: igd_state.kibibytes_per_sec_received,
259 KIBIBYTES_PER_SEC_SENT: igd_state.kibibytes_per_sec_sent,
260 PACKETS_PER_SEC_RECEIVED: igd_state.packets_per_sec_received,
261 PACKETS_PER_SEC_SENT: igd_state.packets_per_sec_sent,
262 PORT_MAPPING_NUMBER_OF_ENTRIES_IPV4:
get_value(
263 igd_state.port_mapping_number_of_entries
None async_set_force_poll(self, bool force_poll)
dict[str, str|datetime|int|float|None] async_get_data(self, list[str]|None entity_description_keys)
None __init__(self, HomeAssistant hass, IgdDevice igd_device, bool force_poll)
str|None async_get_mac_address(self)
None async_unsubscribe_services(self)
None async_subscribe_services(self)
str|None serial_number(self)
float|int|str|None get_value(Sensor sensor, str field)
str get_preferred_location(set[str] locations)
str|None async_get_mac_address_from_host(HomeAssistant hass, str host)
Device async_create_device(HomeAssistant hass, str location, bool force_poll)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)