2019-04-03 15:40:03 +00:00
|
|
|
"""Support for UPnP/IGD Sensors."""
|
2017-06-19 04:32:39 +00:00
|
|
|
import logging
|
|
|
|
|
2020-02-25 01:52:14 +00:00
|
|
|
from homeassistant.const import DATA_BYTES, DATA_KIBIBYTES, TIME_SECONDS
|
2018-10-01 16:25:54 +00:00
|
|
|
from homeassistant.core import callback
|
2019-10-31 19:51:35 +00:00
|
|
|
from homeassistant.helpers import device_registry as dr
|
2018-10-01 16:25:54 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
2017-06-19 04:32:39 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2019-03-25 00:46:15 +00:00
|
|
|
from homeassistant.helpers.typing import HomeAssistantType
|
2019-09-19 06:39:09 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
2017-06-19 04:32:39 +00:00
|
|
|
|
2019-03-21 05:56:46 +00:00
|
|
|
from .const import DOMAIN as DOMAIN_UPNP, SIGNAL_REMOVE_SENSOR
|
2018-08-30 14:38:43 +00:00
|
|
|
|
2017-06-19 04:32:39 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
BYTES_RECEIVED = "bytes_received"
|
|
|
|
BYTES_SENT = "bytes_sent"
|
|
|
|
PACKETS_RECEIVED = "packets_received"
|
|
|
|
PACKETS_SENT = "packets_sent"
|
2018-04-12 22:22:52 +00:00
|
|
|
|
2017-06-19 04:32:39 +00:00
|
|
|
SENSOR_TYPES = {
|
2020-02-25 01:52:14 +00:00
|
|
|
BYTES_RECEIVED: {"name": "bytes received", "unit": DATA_BYTES},
|
|
|
|
BYTES_SENT: {"name": "bytes sent", "unit": DATA_BYTES},
|
2019-07-31 19:25:30 +00:00
|
|
|
PACKETS_RECEIVED: {"name": "packets received", "unit": "packets"},
|
|
|
|
PACKETS_SENT: {"name": "packets sent", "unit": "packets"},
|
2017-06-19 04:32:39 +00:00
|
|
|
}
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
IN = "received"
|
|
|
|
OUT = "sent"
|
2020-02-25 01:52:14 +00:00
|
|
|
KIBIBYTE = 1024
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2017-06-19 04:32:39 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def async_setup_platform(
|
|
|
|
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
|
|
|
):
|
2018-10-01 16:25:54 +00:00
|
|
|
"""Old way of setting up UPnP/IGD sensors."""
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"async_setup_platform: config: %s, discovery: %s", config, discovery_info
|
|
|
|
)
|
2018-10-01 16:25:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
|
|
"""Set up the UPnP/IGD sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2018-10-01 16:25:54 +00:00
|
|
|
@callback
|
|
|
|
def async_add_sensor(device):
|
|
|
|
"""Add sensors from UPnP/IGD device."""
|
|
|
|
# raw sensors + per-second sensors
|
|
|
|
sensors = [
|
|
|
|
RawUPnPIGDSensor(device, name, sensor_type)
|
|
|
|
for name, sensor_type in SENSOR_TYPES.items()
|
|
|
|
]
|
|
|
|
sensors += [
|
|
|
|
KBytePerSecondUPnPIGDSensor(device, IN),
|
|
|
|
KBytePerSecondUPnPIGDSensor(device, OUT),
|
|
|
|
PacketsPerSecondUPnPIGDSensor(device, IN),
|
|
|
|
PacketsPerSecondUPnPIGDSensor(device, OUT),
|
|
|
|
]
|
|
|
|
async_add_entities(sensors, True)
|
|
|
|
|
|
|
|
data = config_entry.data
|
2019-07-31 19:25:30 +00:00
|
|
|
if "udn" in data:
|
|
|
|
udn = data["udn"]
|
2018-12-21 17:25:23 +00:00
|
|
|
else:
|
|
|
|
# any device will do
|
2019-07-31 19:25:30 +00:00
|
|
|
udn = list(hass.data[DOMAIN_UPNP]["devices"].keys())[0]
|
2018-12-21 17:25:23 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
device = hass.data[DOMAIN_UPNP]["devices"][udn]
|
2018-10-01 16:25:54 +00:00
|
|
|
async_add_sensor(device)
|
|
|
|
|
|
|
|
|
|
|
|
class UpnpSensor(Entity):
|
|
|
|
"""Base class for UPnP/IGD sensors."""
|
|
|
|
|
|
|
|
def __init__(self, device):
|
|
|
|
"""Initialize the base sensor."""
|
|
|
|
self._device = device
|
|
|
|
|
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Subscribe to sensors events."""
|
2019-07-31 19:25:30 +00:00
|
|
|
async_dispatcher_connect(
|
|
|
|
self.hass, SIGNAL_REMOVE_SENSOR, self._upnp_remove_sensor
|
|
|
|
)
|
2018-10-01 16:25:54 +00:00
|
|
|
|
2018-10-03 09:07:25 +00:00
|
|
|
@callback
|
2018-10-01 16:25:54 +00:00
|
|
|
def _upnp_remove_sensor(self, device):
|
|
|
|
"""Remove sensor."""
|
|
|
|
if self._device != device:
|
|
|
|
# not for us
|
|
|
|
return
|
|
|
|
|
|
|
|
self.hass.async_create_task(self.async_remove())
|
|
|
|
|
2018-12-21 17:25:23 +00:00
|
|
|
@property
|
|
|
|
def device_info(self):
|
|
|
|
"""Get device info."""
|
|
|
|
return {
|
2019-10-31 19:51:35 +00:00
|
|
|
"connections": {(dr.CONNECTION_UPNP, self._device.udn)},
|
|
|
|
"identifiers": {(DOMAIN_UPNP, self._device.udn)},
|
|
|
|
"name": self._device.name,
|
2019-07-31 19:25:30 +00:00
|
|
|
"manufacturer": self._device.manufacturer,
|
2019-10-31 19:51:35 +00:00
|
|
|
"model": self._device.model_name,
|
2018-12-21 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2018-10-01 16:25:54 +00:00
|
|
|
|
|
|
|
class RawUPnPIGDSensor(UpnpSensor):
|
2018-09-17 20:08:09 +00:00
|
|
|
"""Representation of a UPnP/IGD sensor."""
|
2017-06-19 04:32:39 +00:00
|
|
|
|
2018-09-01 16:13:45 +00:00
|
|
|
def __init__(self, device, sensor_type_name, sensor_type):
|
2018-09-17 20:08:09 +00:00
|
|
|
"""Initialize the UPnP/IGD sensor."""
|
2018-10-01 16:25:54 +00:00
|
|
|
super().__init__(device)
|
2018-09-01 16:13:45 +00:00
|
|
|
self._type_name = sensor_type_name
|
|
|
|
self._type = sensor_type
|
2019-07-31 19:25:30 +00:00
|
|
|
self._name = "{} {}".format(device.name, sensor_type["name"])
|
2017-06-19 04:32:39 +00:00
|
|
|
self._state = None
|
|
|
|
|
|
|
|
@property
|
2018-09-01 16:13:45 +00:00
|
|
|
def name(self) -> str:
|
2017-06-19 04:32:39 +00:00
|
|
|
"""Return the name of the sensor."""
|
|
|
|
return self._name
|
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return an unique ID."""
|
2019-09-03 19:12:51 +00:00
|
|
|
return f"{self._device.udn}_{self._type_name}"
|
2018-09-01 19:20:15 +00:00
|
|
|
|
2017-06-19 04:32:39 +00:00
|
|
|
@property
|
2018-09-01 16:13:45 +00:00
|
|
|
def state(self) -> str:
|
2017-06-19 04:32:39 +00:00
|
|
|
"""Return the state of the device."""
|
2020-01-09 05:51:30 +00:00
|
|
|
if self._state is None:
|
|
|
|
return None
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return format(self._state, "d")
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self) -> str:
|
|
|
|
"""Icon to use in the frontend, if any."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "mdi:server-network"
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self) -> str:
|
|
|
|
"""Return the unit of measurement of this entity, if any."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return self._type["unit"]
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Get the latest information from the IGD."""
|
|
|
|
if self._type_name == BYTES_RECEIVED:
|
|
|
|
self._state = await self._device.async_get_total_bytes_received()
|
|
|
|
elif self._type_name == BYTES_SENT:
|
|
|
|
self._state = await self._device.async_get_total_bytes_sent()
|
|
|
|
elif self._type_name == PACKETS_RECEIVED:
|
|
|
|
self._state = await self._device.async_get_total_packets_received()
|
|
|
|
elif self._type_name == PACKETS_SENT:
|
|
|
|
self._state = await self._device.async_get_total_packets_sent()
|
|
|
|
|
|
|
|
|
2018-10-01 16:25:54 +00:00
|
|
|
class PerSecondUPnPIGDSensor(UpnpSensor):
|
2018-09-01 19:20:15 +00:00
|
|
|
"""Abstract representation of a X Sent/Received per second sensor."""
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
def __init__(self, device, direction):
|
2019-12-10 22:25:06 +00:00
|
|
|
"""Initialize sensor."""
|
2018-10-01 16:25:54 +00:00
|
|
|
super().__init__(device)
|
2018-09-01 16:13:45 +00:00
|
|
|
self._direction = direction
|
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
self._state = None
|
2018-09-01 16:13:45 +00:00
|
|
|
self._last_value = None
|
|
|
|
self._last_update_time = None
|
2018-09-01 19:20:15 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unit(self) -> str:
|
2018-09-02 15:03:31 +00:00
|
|
|
"""Get unit we are measuring in."""
|
2018-09-01 19:20:15 +00:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
2020-02-14 18:00:22 +00:00
|
|
|
async def _async_fetch_value(self):
|
2018-09-01 19:20:15 +00:00
|
|
|
"""Fetch a value from the IGD."""
|
|
|
|
raise NotImplementedError()
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 16:13:45 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return an unique ID."""
|
2019-09-03 19:12:51 +00:00
|
|
|
return f"{self._device.udn}_{self.unit}/sec_{self._direction}"
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 16:13:45 +00:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
"""Return the name of the sensor."""
|
2019-09-03 19:12:51 +00:00
|
|
|
return f"{self._device.name} {self.unit}/sec {self._direction}"
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self) -> str:
|
2017-06-19 04:32:39 +00:00
|
|
|
"""Icon to use in the frontend, if any."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "mdi:server-network"
|
2017-06-19 04:32:39 +00:00
|
|
|
|
|
|
|
@property
|
2018-09-01 16:13:45 +00:00
|
|
|
def unit_of_measurement(self) -> str:
|
2017-06-19 04:32:39 +00:00
|
|
|
"""Return the unit of measurement of this entity, if any."""
|
2020-02-23 20:09:24 +00:00
|
|
|
return f"{self.unit}/{TIME_SECONDS}"
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
def _is_overflowed(self, new_value) -> bool:
|
|
|
|
"""Check if value has overflowed."""
|
|
|
|
return new_value < self._last_value
|
2017-06-19 04:32:39 +00:00
|
|
|
|
2018-04-12 22:22:52 +00:00
|
|
|
async def async_update(self):
|
2018-09-17 20:08:09 +00:00
|
|
|
"""Get the latest information from the UPnP/IGD."""
|
2018-09-01 19:20:15 +00:00
|
|
|
new_value = await self._async_fetch_value()
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 16:13:45 +00:00
|
|
|
if self._last_value is None:
|
|
|
|
self._last_value = new_value
|
2019-09-19 06:39:09 +00:00
|
|
|
self._last_update_time = dt_util.utcnow()
|
2018-09-01 16:13:45 +00:00
|
|
|
return
|
|
|
|
|
2019-09-19 06:39:09 +00:00
|
|
|
now = dt_util.utcnow()
|
2018-09-01 16:13:45 +00:00
|
|
|
if self._is_overflowed(new_value):
|
|
|
|
self._state = None # temporarily report nothing
|
|
|
|
else:
|
|
|
|
delta_time = (now - self._last_update_time).seconds
|
|
|
|
delta_value = new_value - self._last_value
|
2019-07-31 19:25:30 +00:00
|
|
|
self._state = delta_value / delta_time
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
self._last_value = new_value
|
|
|
|
self._last_update_time = now
|
|
|
|
|
|
|
|
|
2018-09-17 20:08:09 +00:00
|
|
|
class KBytePerSecondUPnPIGDSensor(PerSecondUPnPIGDSensor):
|
2018-09-01 19:20:15 +00:00
|
|
|
"""Representation of a KBytes Sent/Received per second sensor."""
|
2018-08-29 19:19:04 +00:00
|
|
|
|
|
|
|
@property
|
2018-09-01 19:20:15 +00:00
|
|
|
def unit(self) -> str:
|
2018-09-02 15:03:31 +00:00
|
|
|
"""Get unit we are measuring in."""
|
2020-02-25 01:52:14 +00:00
|
|
|
return DATA_KIBIBYTES
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
async def _async_fetch_value(self) -> float:
|
2018-09-02 15:03:31 +00:00
|
|
|
"""Fetch value from device."""
|
2018-09-01 19:20:15 +00:00
|
|
|
if self._direction == IN:
|
|
|
|
return await self._device.async_get_total_bytes_received()
|
|
|
|
|
|
|
|
return await self._device.async_get_total_bytes_sent()
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 16:13:45 +00:00
|
|
|
@property
|
2018-10-01 16:25:54 +00:00
|
|
|
def state(self) -> str:
|
2018-09-01 16:13:45 +00:00
|
|
|
"""Return the state of the device."""
|
2018-09-01 19:20:15 +00:00
|
|
|
if self._state is None:
|
|
|
|
return None
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2020-02-25 01:52:14 +00:00
|
|
|
return format(float(self._state / KIBIBYTE), ".1f")
|
2018-09-01 16:13:45 +00:00
|
|
|
|
|
|
|
|
2018-09-17 20:08:09 +00:00
|
|
|
class PacketsPerSecondUPnPIGDSensor(PerSecondUPnPIGDSensor):
|
2018-09-01 19:20:15 +00:00
|
|
|
"""Representation of a Packets Sent/Received per second sensor."""
|
2018-09-01 16:13:45 +00:00
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
@property
|
|
|
|
def unit(self) -> str:
|
2018-09-02 15:03:31 +00:00
|
|
|
"""Get unit we are measuring in."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "packets"
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
async def _async_fetch_value(self) -> float:
|
2018-09-02 15:03:31 +00:00
|
|
|
"""Fetch value from device."""
|
2018-09-01 16:13:45 +00:00
|
|
|
if self._direction == IN:
|
2018-09-01 19:20:15 +00:00
|
|
|
return await self._device.async_get_total_packets_received()
|
2018-09-01 16:13:45 +00:00
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
return await self._device.async_get_total_packets_sent()
|
2018-08-29 19:19:04 +00:00
|
|
|
|
2018-09-01 19:20:15 +00:00
|
|
|
@property
|
2018-10-01 16:25:54 +00:00
|
|
|
def state(self) -> str:
|
2018-09-01 19:20:15 +00:00
|
|
|
"""Return the state of the device."""
|
|
|
|
if self._state is None:
|
|
|
|
return None
|
2018-09-01 16:13:45 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return format(float(self._state), ".1f")
|